Archívum

Archive for 2009. július

.NET szemétgyűjtés – 6.

2009. július 26. vasárnap Hozzászólások kikapcsolva
Az előző rész után tartott hosszabb szünetet követően, a mai alkalmommal egy utolsó, nagyobb témakörbe csapunk bele, amit elképzelhető, hogy csak a következő alkalommal fejezünk be.
 
Sokszor emlegettem már a szemétgyűjtés kapcsán az úgynevezett generációkat (generation), melyek számomra a szemétgyűjtés egyik legizgalmasabb részterületét fedik le. Bizonyám! A CLR szemétgyűjtésén belül ezek a generációk azért jöttek létre, hogy ezzel is segítség az alkalmazás teljesítményét. A szemétgyűjtés lehet gyakori, lehet ritka, de egy biztos, olyan gyorsnak kell lennie, hogy a felhasználó ebből az alkalmazás használata közben lehetőleg semmit se vegyen észre.
 
A generációra alapuló szemétgyűjtés (generational vagy másképp mondva ephemereal garbage collector) az alábbi feltételezésekkel él:
  • Az újabb objektum rövidebb ideig él
  • A régebbi objektum hosszabb ideig él
  • A memóriaterület egy kisebb részén a szemétgyűjtés sokkal gyorsabb, mintha azt az egész területen tennénk

Amikor inicializálódik egy menedzselt heap terület (mondjuk az alkalmazás indulásakor), akkor még nem tartalmaz objektumokat. Amikor létrejön egy új objektum, akkor az kezdetben a 0. generációba tartozik, és a 0. generációnak fenntartott memóriaterületre vagy listába kerül. Kezdetben a 0. generációs lista mérete mondjuk 256 KB (többnyire a CPU L2 cache-hez igazítva, de ez változtatható).

A fenti ábrán láthtó, hogy A – E objektumok vannak a 0. generációban, de a C és E már nem használt, azaz elpusztítható. Amikor egy újonnan létrehozott, mondjuk F objektum miatt a mérete éppen meghaladná a generációs lista korlátját, akkor a szemétgyűjtő elindul. Ezt követően észleli, hogy a C és E már nem kell és eltakarítja őket, majd összetömöríti a listát, így a D objektum közvetlenül a B után kerül. A szemétgyűjtést túlélő A, B és D objektumok úgymond "öregszenek", ezért átkerülnek az 1. generációs listába (a 0. generációból törlődnek), míg az újonnan létrejött "fiatal" F objektum pedig bekerül a 0. generációs listába, ahogy a lenti ábrán ez jól észlelhető.

Az élet persze megy tovább. Újabb és újabb objektumok jönnek létre mondjuk G, H, I, J és K néven, majd a már ismet módszer szerint azok is bekerülnek a 0. generációs listába (elvégre fiataloké a jövő vagy mi a szösz).

Ahogy telik-múlik az idő, tegyük fel, hogy a B, H és J objektumok érvénytelenné és így feleslegessé válnak. A fenti ábra már ezt az áldatlan állapotot mutatja. Mondjuk, hogy eközben új objektum keletkezik L néven, és bekopogtat a 0. generációs lista ajtaján. Ők azonban kiüvöltenek neki az ajtón keresztül, hogy "Hékás, tele van a 256 kilóbájtunk, menj a sunyiba!"  Erre a civakodásra a szemétgyűjtő is felkapja a fejét és odaszól a 0. generációnak, hogy "Hohohó, nem úgy van az!", majd iziben nekilódul, hogy rendet vágjon az elkanászkodott fiatalok között. Mielőtt azonban tovább haladnánk a történetben, megállunk egy pillanatra és szögezzük le, hogy az 1. generációs lista mérete kezdetben mondjuk 2 MB azért, hogy a középkorú objektumos hölgyek és urak is kényelmesen elférjenek benne (megjegyzem ez a mese szempontjából később még fontos infomácó lehet).

No, de ott tartottunk, hogy betelt a 0. generációs lista, és emiatt a szemétgyűjtő elindult a szokásos útjára.  Ekkor az 1. generációban még volt elég szabad hely, így a szemétgyűjtő őt nem bántja, csak a 0. generáció környékén kezd el szaglászni. Többek között azért, mert mint írtam, az a fixa ideája, hogy a 0. genrációban élő új objektumok tartanak ki a legrövidebb ideig, ezért is érdemes velük kezdedni a sort. Az 1. generációban lévőket tehát (bár lenne mit gyűjtögetni ott is) egylőre életben hagyja, de nem pusztán jóindulatból, hanem teljesítmény okokból. Ha nem muszály, feleslegesen nem dolgozik ő sem, és mint sejtjük a kevesebb vizsgálat rövidebb késleltetési időt jelent a folyamat futásában. Megjegyzem, ha egy objektum hivatkozik egy 1. generációba tartozó másik objektumra, akkor természetesen ő megmenekül, bárhol is tanyázik (ez fordítva is igaz, amikor egy régebbi generációban lévő objekum hivatkozik egy újabb generációban lévő másikra).

Azt csak érdekességként említem meg, hogy a Microsoft teljesítménytesztje alapján a 0. generációban végrehajtott szemétgyűjtő művelet kevesebb, mint 1 századmásodpercig tart. A cél az, hogy ez a tevékenység ne igényeljen több időt, mint egy közönséges lapváltás (page fault).

A generációs szemétgyűjtő feltételezése, hogy a hosszú életű objektumok tovább tartanak. Valószínűbb, hogy az 1. generációban lévők tartósabbak, mint a 0. generációban helyet foglaló társaik. Másképp mondva, az 1. generációban valószínűleg nem, vagy csak nagyon ritkán lenne eltakarítandó objektum, azaz felszabadítandó memóriaterület, így annak örökös zargatása tiszta időpocsékolás lenne.

A fenti ábrán látható, hogy akik a 0. generációban túlélték a szemétgyűjtést, azok átkerültek az 1. generációba, és így a 0. generáció szabadon és boldogan várja az újonnan beköltöző objektumokat. Továbbá, mivel a szemétgyűjtő nem vizsgálta az 1. generációt, így az eredetileg ott lévő, és egyébként már régóta nem használt objektumok (mint amilyen a B) érintetlenül maradtak.

A történet közepén felbukkanó L objektumot egy picit elhanyagoltuk, de ez most már így marad a következő alkalomig (a fene se gondolta, hogy ennyit fogok írni).

Most már becsszóra az utolsó következik.

Folytatjuk…

Kategóriák:Programming

.NET szemétgyűjtés – 5.

2009. július 12. vasárnap Hozzászólások kikapcsolva
Az ember azt hinné, hogy négy rész után túl van már a lényegen, pedig nem.
 
Elöljáróban annyit, hogy a CLR és a szemétgyűjtő viselkedését bizonyos fokig szabályozhatjuk, ha akarjuk (de ne akarjuk). Ugyanez igaz az objektumok életciklusára is, melyet kívülről némileg befolyásolhatunk (főleg nem menedzselt kódokkal való együttműködés esetén). Akit érint, az sertepertéljen picit a System.Runtime.InteropService, azon belül is a GCHandle környékén.
 
Bizonyára emlékszünk milyen felirat van néhány temető bejárata felett, ugye? Ha nem, akkor megsúgom: Feltámadunk! Na igen, jó is lenne. Amennyire én tudom ez idáig csak egyvalakinek sikerült, de annak is már lassan 2000 éve. Nem úgy a.NET objektumoknál. Náluk ez akár mindennapos is lehet, ha a teremtőjük úgy akarja.
 
Na, de térjünk vissza a valóságba. Egy objektum élhet, meghalhat, megint élhet és újra meghalhat. A halott objektumot az élők sorába visszahozó műveletet felélesztésnek (resurrection) nevezik. Amikor szemétgyűjtő elhelyez egy hivatkozást a freachable sorba, akkor az objektum még vidáman elérhető, és visszahozható az életbe. Ehhez az szükséges, hogy a finalizer kód hozzáférjen az adott objektum mezőihez (ott gyökér, azaz root bejegyzést létrehozva). Amennyiben ilyen bejegyzés NINCS, akkor a finalizer metódus végrehajtása után az objektum örökre meghalt, nem elérhető tovább, és nem is nem éleszthető fel többé.
 
Példa:

internal class Proba
{
  ~Proba()
   {
     Program.objRefTarolo = this;
     GC.RegisterForFinalize(this); // Ez vagy itt van, vagy nincs…
   }
}

public static class Program
{
  public static object objRefTarolo;
 
  public static void Main()
  {
    Proba proba = new Proba();
    …
  }
}
A fenti "Proba"  osztály "proba" példánya éltetrevaló egy jószág, ugyanis a finalizer metódusában lévő értékadás hatására a szemétgyűjtő nem fogja őt azonnal eltakarítani, azaz még a finalizer művelet után is lehet használni. Peresze ez azért nem egy normális állapot, mivel a finalizer végrehajtása után az objektum olyan állapotba kerülhet (és nem csak a stressz miatt, hogy őt most épp kinyírják, hanem egyébként is), hogy az előre nem látható, megjósolhatatlan következményekhez vezethet. 
 
Ha valaki a finalizer végére odabiggyeszti a "GC.RegisterForFinalize()" metódushívást, ennek hatására az objektum újra, önként odaáll a kivégzőosztag elé, és így a szemétgyűjtő ismét lövöldözhet rá, már ha az "objRefTarolo" mező értékét valamikor null-ra állítjuk. FIGYELEM! Nem szép dolog ilyeneket tenni (bár néha tényleg hasznos lehet), úgyhogy kerüljük el ahol csak lehet.
 
A következő, és egyben utolsó részben már istenbizony a generációkról fogok írni.
 
Folytatjuk…
Kategóriák:Programming

.NET szemétgyűjtés – 4.

2009. július 5. vasárnap Hozzászólások kikapcsolva
A múlt héten ott hagytuk abba, hogy ígértem egy mesét. Ez a mai mese a szemétgyűjtés során végrehajtott finalizer folyamat pontos menetéről, illetve az úgynevezett freachable sor vagy eredeti nevén "freachable queue" lényegéről szól.
 
Az előző rész végén volt egy ábra, rajta a C, E, F, I és J objektumokkal, amelyek hivatkozása szerepel az úgynevezett finalization listában. Ez azt jelenti, hogy a hivatkozott objektumoknak van finalizer metódusa, melyekkel majd a szemétgyűjtés során komolyan törődni is kell. Amikor a szemétgyűjtés elindul, akkor a B, E, G, H és I objektumok a korábban már ismertetett módon, azonnal pusztulásra vannak ítélve. Mindeközben a  CLR azért végignézi a finalization listát is, hogy megkeresse mely törlésre jelölt objektumok mutatói szerepelnek rajta. Amikor talál egyet, akkor törli ebből a listából, és átrakja a másik, úgynevezett "Freachable" sorba (ejtsd: "F-reachable" sorba). Itt tehát azok az objektumok mutatói tanyáznak, melyek megszűntetésre vannak ítélve, és milyen pech, e mellett még van finalizer metódusuk is.
 

A fenti ábrán ez az állapot látható illetve az is, hogy a B, G és H objektumok a szemétgyűjtés következményeképp már megszűntek (tessék összevetni a korábbi bejegyzésben lévő ábrával), ezáltal a memória helyük felszabadult, valamint a felszabadított hely tömörítése is végrehajtódott (nekik nem volt finalizer metódusuk). Azonban az E, I és J objektumok bár első körben szintén pusztulásra voltak ítélve, mégis megmaradtak. Ők még nem tudják, de mi már igen, hogy a túlélés miatti örvendezést azért nem kellene annyira túlzásba vinni, mivel ezt csakis kizárólag a létező finalizer metódusaiknak köszönhetik (ráadásul nem is tart valami sokáig).
 
Az objektumok említett finalizer metódusainak meghívására egy speciális, külön erre a célra fenntartott, magas prioritású CLR-szál (CLR-thread) szolgál. A külön szálnak és az említett prioritásnak az a célja, hogy elkerülje a normál prioritású szálak szinkronizációjakor felmerülő versengéseket. Amikor a freachable sor üres (gyakori eset), akkor ez a szál békésen alszik, fittyet hányva a körülötte tombóló szemétgyűjtő által végrehajtott erőszaknak. Abban a pillanatban azonban, ahogy egyetlen egy objektum mutató is bekerült a freachable sorba, a nevezett szál tüstént megindul útjára, és szépen sorban meghívja minden egyes objektum finalizer metódusát. A sikeres meghívást és végrehajtást követően az adott objektum mutatója törlődik a freechable sorból.
 
Bár az eddigiekből kiderülhetett, de azért mégsem árt kihangsúlyozni, hogy azok az objektumok, melyek benne vannak a freachable sorban gyakorlatilag már korábban puszulásra lettek ítélve. Ezt a programozónak a finalizer metódus írásakor illő lenne tudomásul vennie, és emiatt a túl sok, de főleg összetett és időigényes műveleteket elkerülnie (a szálkezelés, kivétel előidézés, hosszú ideig tartó vagy összetett adatbázis SQL utasítások végrehajtása nem szép dolog, de természetesen a szokásos erőforrás felszabadításokra irányuló tevékenységek mehetnek).
 
Megjegyzés: A "Freachable" szóból az "F" a finalizer-re, a "reachable" pedig arra utal, hogy az objektum elvileg még létezik és elérhető, hiszen a memóriabeli helye ekkor még nincs eltakarítva, felszabadítva (legalábbis néhány pillanatig biztosan nem). Ugyanígy a pillanatnyi túlélők klubjába tartoznak még azok az objektumok is, amelyekre a finalizer metódust tartalmazó objektum mezői, vagy helyi változói éppen hivatkoznak.
 
Amikor a szemétgyűjtés legközelebb ismét elindul látja (vagy pont nem látja, de ez már filozófiai kérdés), hogy a korábban felszabadításra jelölt objektumoknak már nincs bejegyzése a finalization listában, illetve a freachable sorban sem, így jó szamurájhoz méltóan, kénye-kedve szerint lekaszabolhatja őket. Ekkor (vagyis a következő szemétgyűjtés során), és csakis ekkor történhet meg az előző szemétgyűjtéskor már törlésre jelölt objektumok helyének tényleges felszabadítása, és az újabb memória tömörítés végrehajtása.
 

Kiderült tehát, hogy egy finalizer metódussal rendelkező objektum megszűntetéséhez ténylegesen legalább két szemétgyűjtési fázis szükséges. Fontos megjegyezni még, hogy a finalizer metódus elkészítését az úgynevezett "Dispose pattern" támogatja, melyet rengeteg szakirodalom taglal, ezért én most nem is szeretnék kitérni rá.
 
Hogy teljes legyen a kép, legközelebb a halott objektumok feltámasztásáról, és végre valahára a már sokat emlegetett generációs listákról lesz szó.
 
Folytatjuk…
Kategóriák:Programming

“YEAH! I use Linux! F**k Microsoft!”

2009. július 1. szerda Hozzászólások kikapcsolva
Csak kimondta már valaki (rajtam kívül is)! Na nem, természetesen nem a "YEAH! I use Linux! F**k Microsoft!" mondatról van szó, hanem… de ne fussunk az események elébe. Örülök, hogy legalább néhányan a Linux Journal lapjain érzik a problémát, ami bizony létezik, ráadásul igaz. Nagyon igaz. Tessék elolvasni.
 
"The good Linux users I know don’t even talk about Microsoft. Never will you see a "windoze", "winblows", "M$" or "Micro$oft" [esetleg Magyarországon "mikrofos"] in anything they blog about. To note, those that do write those childish things are morons because as said above, nobody cares. You don’t see me calling Linux users tux turds, penguin poopers or GUI-challenged, do you?"
 
Részletek itt.
 
Sokszor, sokat írtam már e helyütt, hogy a hithű, Linux-os rajongóknak gyerekes módon nem másokkal, nem a Microsoft-tal, hanem saját magukkal kellene foglalkozniuk. A pingvines közösség túlnyomó többsége vagánynak tartja minősíthetetlen stílusban jellemezni az ellenfeleket (aki nem velünk az ellenünk, ugye), ami persze jottányit se emeli az emberek szemében sem az ő, sem pedig a Linux ázsióját. Aki nem hiszi, olvasgasson pár napig ezen a helyen, és majd meglátja, hogy a fenti cikk írójának igaza volt. Pont ez az egyik ok, ami miatt én is irtózom tőlük, és amiért néha kissé… hmm… mondjuk úgy – erősebben nyomom a billentyűket.
Kategóriák:Computers and Internet