Vzhledem k serveru, na kterém toto čtete, je asi každému zřejmé, že tento článek není o komunálních službách. Ano, uhodli jste, jde o garbage collection (GC).
Především je nutné zmínit, že správa zdrojů se netýká jen paměti, ale například i souborů, připojení k databázi apod. GC řeší pouze správu paměti, o ostatní zdroje musí postarat vývojář. Většina vývojářů, i začínajících, na otázku, co je GC, odpoví, že „část programu nebo běhového prostředí, která prochází hierarchii objektů od kořenů, označuje ty dosažitelné a likviduje ostatní“. To je ovšem jako tvrdit, že hasič je člověk, který jezdí v červeném autě a stříká vodu. Odhlédneme-li od konkrétních implementací, úkolem GC je umožnit opětovné využití paměti, která již není potřebná.
Nejjednodušším GC je tzv. „null garbage collector“, který nedělá vůbec nic. Pokud bychom měli v počítači neomezné množství paměť, byl by jednoznačně nejlepším. Prakticky každý program, který nepotřebuje více paměti, než je fyzicky k dispozici, GC nepotřebuje. Není to sice fér k ostatním aplikacím, protože OS je buď odsune do virtuální paměti, nebo ukončí (některé OS, zejména mobilní, virtuální paměť nemají vůbec), ale vlastní program korektně proběhne a skončí.
Problém se správou paměti je, že v postatě každý program je natolik komplexní, že vývojář ztrácí o paměti přehled. Ohledně správy paměti existují dva extrémy. Ve většině případů je ideální uvolňovat paměť hned, jakmile není potřebná (objekt už nepotřebujeme). Druhým extrémem je neuvolňovat vůbec.
Ideál téměř bezchybně splňuje alokace na zásobníku, až na to, že takový objekt vždy „žije“ jen chvíli, lokálně, neexistuje způsob, jak jeho život prodloužit, pokud jej potřebujeme i jinde. Řešením je takové objekty kopírovat, buď ze zásobníku na haldu (jako u bloků v clangu/LLVM), nebo opět na zásobník. Kopírování je pochopitelně relativně drahé, i když například při návratu z funkce moderní překladače return optimalizují a nic se nekopíruje. (Doporučuji experiment s třídou vector v nativní STL a STL/CLR, výsledek bude v obou případech v MSIL, ale STL/CLR je mnohem rychlejší – ne algoritmicky, ale proto, že se využívají jen řízená data, a navíc odpadá dealokace).
Objekty na haldě jsou mnohem dražší. Ne nutně alokace (záleží na použitém alokátoru nebo GC), ale i rušení objektů nebo kopírování referencí. Blízko ideálu je autorelease pool, prvně použitý v OpenStepu (původní NeXTStep nic takového neměl). Nepotřebné objekty se sice neruší v okamžiku, kdy už nejsou potřebné (což by bylo ideální z hlediska spotřeby paměti), ale aspoň se ruší deterministicky (i když ne z pohledu programátora). Díky rozdělení paměti na zóny (tehdy, dnešní runtime už funguje jinak) navíc nebyly problémy s defragmentací.
Plně automatické GC alokují rychle a dobré implementace i uvolňují rychle. To je vykoupeno obrovskou složitostí implementace (např. .NET má dva zásobníky a pět hald), to ale programátora píšícího běžné aplikace moc zajímat nemusí. Větším problémem je paměťová náročnost (angl. memory footprint). Empiricky je dokázáno, že systém s GC dosáhne stejného výkonu jako stejný kód bez GC, pokud má k dispozici 5× více paměti. Prakticky ovšem mírně nižší výkon téměř nikdy nevadí a zmíněný fakt je tak čistě akademický. Navíc paměti je v počítačích dost (méně už na mobilních zařízeních, kde navíc čím více paměti, tím větší spotřeba energie).
Dost bylo teorie, jaké jsou rady pro efektivní využívání paměti? To silně závisí na jazyku. V C++ používat RAII a const („copy semantics“ ne vždy kopíruje, např. kolekce optimalizují kopírování klíčů a hashů u neměnitelných objektů), v .NET používat „value“ typy (většinou vznikají na zásobníku a jejich životní cyklus proto není řízen GC), v C++/CLI používat „stack semantics“ (to sice nemá nic společného se zásobníkem, „ref“ třídy vznikají vždy na haldě, ale aspoň zajistíme deterministickou správu nepaměťových zdrojů). V Javě se toho moc dělat nedá, ale nejnovější verze překladače zásobník používá podle vlastního uvážení (s využitím escape analýzy).
NB: Plně automatický GC není nic nového, měly jej např. Lisp či Smalltalk.
Jen drobnost, neříká se virtuální paměť, ale odkládací soubor, nebo odkládací paměť, nebo cokoliv jiného. Termín virtuální paměť totiž zahrnuje i fyzickou pamět pro stránky nacházející se momentálně v paměti.
Jiště, je na diskuzi, zda paměť procesu, která se nazývá jako virtuální proto, že nemusí vždy být realizovaná ve fyzické paměti, ve skutečnost neleží celá v odkládacím souboru a fyzická paměť se používá jen jakási další úroveň procesorové cache. Zatím žádný známý operační systém tohle do důsledku nedělá, Linux se swapem je nouze cnost, lépe jsou na tom Windows, přesto má paměťová stránka procesu ve fyzické paměti větší privilegia než stránka souborové cache ... i když vlastně mezi nimi není velký rozdíl.
Autor se zabývá vývojem kompilátorů a knihoven pro objektově-orientované programovací jazyky.
Přečteno 36 203×
Přečteno 25 362×
Přečteno 23 796×
Přečteno 20 178×
Přečteno 17 875×