Roskankeräysstrategioihin liittyvät erot¶
PyPyn käyttämät tai toteuttamat roskankerääjät eivät perustu viittauslaskentaan, joten objekteja ei vapauteta välittömästi, kun ne eivät ole enää tavoitettavissa. Tämän ilmeisin vaikutus on se, että tiedostoja (ja socketteja jne.) ei suljeta pikaisesti, kun ne menevät soveltamisalan ulkopuolelle. Kirjoittamista varten avattujen tiedostojen tiedot voivat jäädä hetkeksi niiden tulostuspuskureihin, jolloin levyllä oleva tiedosto näyttää tyhjältä tai typistetyltä. Lisäksi saatat saavuttaa käyttöjärjestelmäsi rajoituksen samanaikaisesti avattujen tiedostojen lukumäärälle.
Jos olet debuggaamassa tapausta, jossa ohjelmasi tiedostoa ei suljeta oikein, voit käyttää komentorivioptiota -X track-resources
. Jos se annetaan, tuotetaan ResourceWarning
jokaisesta tiedostosta ja socketista, jonka garbage collector sulkee. Varoitus sisältää pinojäljen siitä kohdasta, jossa tiedosto tai socket luotiin, jotta on helpompi nähdä, mitkä ohjelman osat eivät sulje tiedostoja eksplisiittisesti.
Tämän eron korjaaminen CPythoniin on periaatteessa mahdotonta ilman, että roskienkeruuseen pakotetaan areference-counting-lähestymistapa. Vaikutus, jonka saat CPythonissa, on selvästi kuvattu toteutuksen sivuvaikutukseksi eikä kielen suunnittelupäätökseksi: ohjelmat, jotka luottavat tähän, ovat periaatteessa vääriä. Olisi liian voimakas rajoitus yrittää pakottaaCPythonin käyttäytyminen kielispekseissä, koska sillä ei ole mitään mahdollisuuksia tulla hyväksytyksi Jythonissa tai IronPythonissa (tai missään muussa Pythonin portissa Javalle tai.NET:lle).
Jopa naiivi ajatus täydellisen GC:n pakottamisesta silloin, kun lähestymme vaarallisen lähelle käyttöjärjestelmän rajaa, voi olla hyvin huono joissakin tapauksissa. Jos ohjelmasi vuotaa voimakkaasti avoimia tiedostoja, niin se toimisi, mutta pakota täydellinen GC-sykli joka n:nnen vuotaneen tiedoston kohdalla. N:n arvo on vakio, mutta ohjelma voi viedä mielivaltaisen määrän muistia, mikä tekee täydellisestä GC-syklistä mielivaltaisen pitkän. Lopputuloksena on, että PyPy käyttäisi mielivaltaisen suuren osan ajoajastaan GC:ssä – hidastaen varsinaista suoritusta, ei 10 %:lla eikä 100 %:lla eikä 1000 %:lla, vaan olennaisesti millä tahansa kertoimella.
Tietämyksemme mukaan tähän ongelmaan ei ole parempaa ratkaisua kuin ohjelmien korjaaminen. Jos se esiintyy kolmannen osapuolen koodissa, tämä tarkoittaa sitä, että on mentävä tekijöiden luo ja selitettävä heille ongelma: heidän on suljettava avoimet tiedostonsa, jotta ne voidaan ajaa millä tahansa muulla kuin Python-pohjaisella Python-toteutuksella.
Tässä on lisää teknisiä yksityiskohtia. Tämä ongelma vaikuttaa tarkkaan aikaan, jolloin __del__
metodeja kutsutaan, mikä ei ole luotettava tai oikea-aikainen PyPyssä (eikä Jythonissa eikä IronPythonissa). Se tarkoittaa myös sitä, ettäheikot viittaukset saattavat pysyä elossa hieman odotettua kauemmin. Tämä tekee ”heikoista sijaisviittauksista” (kuten weakref.proxy()
palauttaa) hieman epähyödyllisempiä: ne näyttävät pysyvän elossa hieman pidempään PyPyssa, ja yhtäkkiä ne ovatkin kuolleet ja nostavat ReferenceError
seuraavalla käyttökerralla. Kaikkien heikkoja välityspalvelimia käyttävien koodien on otettava tällaiset ReferenceError
huolellisesti kiinni kaikissa niitä käyttävissä paikoissa. (Tai vielä parempi, älä käytäweakref.proxy()
ollenkaan; käytä weakref.ref()
.)
Huomaa yksityiskohta dokumentaatiossa weakref-callbackien osalta:
Jos callback on annettu eikä None, ja palautettu weakrefobjekti on vielä elossa, callbackia kutsutaan, kun objekttia ollaan lopettamassa.
On tapauksia, joissa CPythonin refcount-semantiikasta johtuen weakrefdiesähtyy välittömästi ennen tai jälkeen niiden objektien, joihin se osoittaa (tyypillisesti jollain ympyräviittauksella). Jos se sattuu kuolemaan juuri sen jälkeen, silloin kutsutaan takaisinkutsu. PyPy:ssä vastaavassa tapauksessa sekä objekti että weakref katsotaan kuolleiksi samaan aikaan, eikä takaisinkutsua kutsuta. (Issue #2030)
GC:n erosta aiheutuu muutama ylimääräinen vaikutus. Tärkeintä on se, että jos objektilla on __del__
, sitä __del__
ei koskaan kutsuta useammin kuin kerran PyPyssä; mutta CPython kutsuu samaa __del__
useita kertoja,jos objekti herätetään henkiin ja se kuolee uudestaan (ainakin näin on luotettavasti vanhemmissa CPythoneissa; uudemmat CPythonit pyrkivät kutsumaan destruktoreja korkeintaan kerran,mutta vastakkaisia esimerkkejä on olemassa). __del__
-metodit kutsutaan ”oikeassa” järjestyksessä, jos ne kohdistuvat toisiinsa viittaaviin objekteihin, kuten CPythonissa, mutta toisin kuin CPythonissa, jos toisiinsa viittaavien objektien kuollut sykli on kuollut, niiden __del__
-metodit kutsutaan joka tapauksessa;CPython sen sijaan laittaisi ne gc
moduulin garbage
luetteloon. Lisätietoa löytyy blogista .
Huomaa, että tämä ero saattaa näkyä epäsuorasti joissakin tapauksissa. Esimerkkinä mainittakoon, että keskeneräiseksi jätetty generaattori kerätään – jälleen kerran – roskakoriin myöhemmin PyPyssä kuin CPythonissa. Voit nähdä eron, jos yield
-avainsana, johon se on keskeytetty, on itse try:
– tai with:
-lohkon sisällä. Tämä näkyy esimerkiksi ongelmana 736.
Käytettäessä oletus GC:tä (nimeltään minimark
), sisäänrakennettu funktio id()
toimii kuten CPythonissa. Muilla GC:llä se palauttaa numeroita, jotka eivät ole todellisia osoitteita (koska objekti voi liikkua useita kertoja)ja sen kutsuminen usein voi johtaa suorituskykyongelmiin.
Huomaa, että jos sinulla on pitkä ketju objekteja, joista jokaisella on viittaus seuraavaan objektiin ja jokaisella on __del__
, PyPyn GC toimii huonosti. Positiivisella puolella, useimmissa muissa tapauksissa benchmarkit ovat osoittaneet, että PyPyn GC toimii paljon paremmin kuin CPythonin.
Toinen ero on, että jos lisäät __del__
:n olemassa olevaan luokkaan, sitä ei kutsuta:
>>>> class A(object):.... pass....>>>> A.__del__ = lambda self: None__main__:1: RuntimeWarning: a __del__ method added to an existing type will not be called
Jopa vielä hämärämpi: sama pätee vanhan tyylisiin luokkiin, jos kiinnität __del__
:n instanssiin (CPythonissakaan tämä ei toimi uusilla luokkatyypeillä). PyPy:ssä saat RuntimeWarningin. Korjataksesi nämä tapaukset varmista vain, että luokassa on aluksi __del__
-metodi (vaikka se sisältäisi vain pass
; sen korvaaminen tai ohittaminen myöhemmin toimii hyvin).
Viimeinen huomautus: CPython yrittää tehdä gc.collect()
automaattisesti, kun ohjelma päättyy; PyPy ei. (Sekä CPythonissa että PyPyssä on mahdollista suunnitella tapaus, jossa tarvitaan useita gc.collect()
ennen kuin kaikki objektit kuolevat. Tämän vuoksi CPythonin lähestymistapa toimii kuitenkin vain ”suurimman osan ajasta”)
.