Deprecated: mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead in /home/www/novacisko.cz/subdomains/bredy/init.php on line 11

Warning: mysql_connect(): Headers and client library minor version mismatch. Headers:50562 Library:100020 in /home/www/novacisko.cz/subdomains/bredy/init.php on line 11

Warning: Cannot modify header information - headers already sent by (output started at /home/www/novacisko.cz/subdomains/bredy/init.php:11) in /home/www/novacisko.cz/subdomains/bredy/index.php on line 38
Garbage collector v C++ - Popis API tříd kolekce LightSpeed::GC - Bredyho blog - Ondřej Novák
Bredyho blog - Ondřej Novák

Garbage collector v C++ - Popis API tříd kolekce LightSpeed::GC

V tomto článku si představíme rozhrani Garbage Collectoru v knihovně LightSpeed. Princip GC byl přidstaven v předchozím díle


Soubory implementace GC v knihovně LightSpeed

Na konci minulého dílu jsem uvedl lokaci, kde lze najít zdrojáky k již fungující verzi GC v C++. Připomenu ještě jednou onen odkaz. Připomínám, že jde o odkaz na Subversion repozitář

http://light-speed.svn.sourceforge.net/svnroot/light-speed/branches/devel/lightspeed/gc

Hlavičkové soubory: Nacházíme zde soubor gc.h, kde najdeme základní API pro ovládání GC na nižší úrovni, kdy potřebujeme přímo manipulovat se stavem GC. Mnohem užitečnější pro nás je soubor gcptr.h, kde nalezneme zejména deklaraci ukazatele, který automaticky informuje GC o vznikajících a zanikajících referencí. Soubor gcmt.h nás pak bude zajímat v případě, že pracujeme s GC ve vícevláknové aplikaci a potřebujeme ke GC přistupovat nahodile z různých vláken. Soubor gcstl.h pak poskytuje alokator umožňující používat kontejnery též spravované pomocí GC. (proč je alokátor potřebný si vysvětlíme později).

Soubory CPP: je potřebné hlídat zejména kvůli tomu, aby při linkováni nechyběly některé použité symboly. gc.cpp obsahuje zdrojový kód vlastní GC, tedy víceméně algoritmu popsaného v předchozím díle. Soubor gcmt.cpp pak obsahuje dodatečnou implementaci pro vícevláknové aplikace.

Objekt garbage collectoru

Při návrhu API byl kladen důraz na dvě podmínky, které musí API umožňovat

  1. Snadnou a intuitivní správu objektů bez zbytečného kódu navíc.
  2. Snadnou rozšířitelnost.

Nemusím snad dokazovat, že tyto dvě podmínky jdou trochu proti sobě. Pro splnění první podmínky je třeba zajistit, aby GC byl implementován víceméně jako singleton, zároveň však jeho akční rádius nemusí nutně pokrývat celou aplikaci. Pak by totiž vlastnosti nastavené pro jednu část aplikace nemusely být výkonové přínosné pro jinou část aplikace.

Pro snadné použití tedy nemůžeme požadovat po uživateli, aby celou aplikací šířil nějakou referencei na instanci garbage collectoru. Tato instance musí být globální, zároveň však nesmí zde být omezení na to, že musí být jediná.

Abysme splnili druhou podmínku, musíme mít možnost určit, jakou instanci GC budeme považovat za globální, není tedy možné zvolit prostě jednu a programátore starej se. Zároveň jsem chtěl ale nějakým způsobem zakrýt interní část objektu GC, který obsahuje spoustu definicí nutých pro běh GC, ale které je naprosto zbytečné zveřejňovat.

Povšimněme si v souboru gc.h zejména rozhraní IGCService. Toto rozhraní definuje všechny základní operace, které naše GC umí, zároveň tak předepisuje, které operace musí libovolné rozšíření umět. Jsou to:

registerObject
Registruje objekt pod správu GC. Každý objekt GC vidí jako úsek paměti začínající na nějaké adrese a mající nějakou delků. Valná většina objektů toto splňuje, nicméně některé mohou alokovat nějakou extra paměť na jiných adresách. Pak je třeba tuto informaci dodat pomocí funkce linkMemoryBlock.
unregisterObject
Ruší registraci objektu v GC. I když se zdá, že funkce nemá opodstatnění, protože GC nám zpravidla bude objekty ničit sám, přesto se funkce hodí. Zejména tam, kde registrujeme objekty do GC spekulativně s tím, že nevíme, zda objekty budeme používat s GCPtr. Dokud totiž objekt nespojíme s referencí, GC takový objekt neumí sám uvonit (až po uvolnění reference). Rušit registraci má také smysl v případě různych chybových stavů, kdy k registraci došlo, ale objekt ještě nebyl předán GCPtr. Pokud by nebylo možné objekt odregistrovat, pak by při pokusu uvolnit již neexistující objekt z GC způsobilo chybu.
addLink
removeLink
Jsou funkce, které hlásí stavy různých referencí
collect
Je velmi důležitá funkce, která ručně vynutí úklid zanechaných objektů. Není samozřejmě nutné ji volat explicitně (funguje i autocollect), ale někdy se to hodí.

Dál jsou tam funkce nastavení a funkce pro podporu ladění (debug). Je dobré toto rozhraní znát pro případ, že bychom se rozhodli napsat rozšíření GC, nicméně mnohem častěji budeme s GC pracovat pomocí GCPtr.

Práce s GCPtr

GCPtr je šablona emulující obyčejný ukazatel. Pro použití šablony píšeme:

using namespace LightSpeed;

GCPtr<MujObjekt> ptr;

Tímto jsme deklarovali ukazatel spravovaný pomocí Garbage Collectoru. Programátor zběhlý v používání chytrých ukazatelů nevidí žádnou komplikaci. A tak nepřekvapí, že s objektem se pracuje stejně jako s běžným ukazatelem. Výčet všech operací lze najít ve třídě LightSpeed::Pointer<T>.
Uveďme si jen některé zvláštnosti. Nejdůležitější operací je prvotní přiřazení vzniklého objektu do GCPtr:

GCPtr<MujObjekt> ptr = new MujObjekt;

Někoho překvapí, že v kontrastu jiných implementací GC, tato nevyžaduje speciální deklaraci operátoru new. Toto GC prostě nepotřebuje spravovat vlastní alokace. Postačí mu vědět, kde v paměti se který objekt nachází, aby následně uměl tato místa propojit referencemi. Uvolnění nereferencovaného objektu se pak provádí pomocí operátoru delete, případně lze toto výchozí chování změnit.

Právě nutnost znát místo, kde je objekt alokován přináši drobné omezení v tom, jak může být objekt přiřazen do GCPtr. Uveďme si správné a špatné varianty

GCPtr<MujObjekt> ptr = new MujObjekt; //správně

GCPtr<Base> ptr = new Derived; //správně (Derived dědí Base)

Derived *tmp = new Derived;
GCPtr<Base> ptr = tmp; //správně

Base *tmp = new Derived;
GCPtr<Base> ptr = tmp; //CHYBA!!!

Base *tmp = GC<>::registerObject(new Derived);
GCPtr<Base> ptr = tmp; //správně

Ona chybná varianta vyplývá ze způsobu, jak GCPtr rozpoznává adresu a velikost objektu. Pokud předáváme objekt ve variantě odkazu na předka, GCPtr není schopen zjistit tyto informace pro původní objekt. Poslední varianta je správna, protože voláním funkce registerObject dopředu informujeme GC, kde objekt začíná a jak je velký. Pak již není problém ukazatel přiřadit do GCPtr. Použití registerObject se doporučuje spojit s voláním unregisterObject v destruktoru. Pro zjednodušení práce lze využít speciální šablonu GCObject

class Derived: public Base, public GCObject<Derived> {
public:
};

Tento objekt se automaticky registruje při vzniku a automaticky odregistruje při zániku.
(K tomu je třeba dodat, že funkce registerObject a unregisterObject jsou relativně bezpečné a GC dokáže zpracovat i vícenásobné volání register či unregister. Přitom platí, že register se provede vždy, když adresový rozsah rozšiřuje naposledy registrovaný adresový rozsah a první unregister provádí odregistrování celého objektu, každý další je ignorován).

ostatní operace GCPtr

Reset:

using LightSpeed::NullType;
GCPtr<T> ptr = nil;

Hodnota ukazatele:

T *val = ptr;

nebo

value(ptr);

Test na nulovou hodotu

using LightSpeed::NullType;
if (ptr == nil) {....}
if (ptr != nil) {....}

GC a vlastní alokátory

GC umožňuje spravovat objekty alokované vlastními alokátory. Existují víceméně dvé cesty:

  1. Deklaraci new a delete jako členské funkce třídy.
  2. Změna způsobu rušení objektu tak aby odpovídala způsobu alokace.

První způsob je zřejmý. Pokud deklarujeme vlastní operátory new a delete, GC je bude respektovat. Je třeba jen dodržet to co bylo napsáno v předchozí kapitole. Právě zajištění správného operátoru delete lze jen v případě, že GCPtr je inicializován ukazatelem správného typu.

V druhém případě si lze vypomoci volitelným parametrem šablony GCPtr. Ten určuje funktor, kterým se provádí destrukce objektu. Výchozí hodnota je ReleaseDeleteRule, tento funktor právě ničí objekty operátorem delete. Lze zvolit jiný funktor a to buď z kolekce releaseRules.h, nebo si napsat vlastní

GCPtr<MujObjekt, ReleaseByFunctionRule> ptr = MujObjekt::Allocate();

Collect a autocollect

Operaci collect si lze vynutit zavoláním funkce collect() a to přímo přes libovolnou instanci GCPtr, nebo přes objekt GC<>. V obou případech se jedná o metodu třídy (deklarace static), čili nezáleží na stavu příslušného objektu.

GCPtr<MujObjekt> ptr;
ptr.collect();

GCPtr<MujObjekt>::collect();

GC<>::collect()

Čas od času je proveden collect automaticky. Lze nastavit tři podmínky, kdy se collect provede

počet alokovaných objektů
collect se provede po alokování určitého počtu objektů od posledního collect. Výchozí hodnota je 150
čas od posledního collect
pokud absolutní čas od posledního collect překročí nastavenou hodnotu. Výchozí hodnota je 30000ms (30s)
celkové množství naalokované paměti
pokud celkové množství naalokované paměti od posledního collect překročí nastavenou hodnotu. Výchozí hodnota je 1MB.

Změna nastavení se provede pomocí GC<>::setConfig().

Poznámka
Prázdné collecty (tedy pokud se zjistí, že není co dělat) mohou být velice rychlé, proto výchozí hodnota pro počet objektů je tak nízký. GC nevychází optimálně, pokud počet objektů bez podpory kostry je velký, protože to znamená větší objem práce a větší množství cyklů v rámci collect. Častěji prováděný collect zase trpí neefektivitou při opakovaném zneplatňování a hledání a koster.

Volba instance GC

Jak už bylo řečeno, GC je koncipován jako singleton. V danem okamžiku existuje jediná instance objektu GC, který činnost GC implementuje. Nicméně pomocí GCPtr můžeme zvolit i jinou instanci a to pomocí dalšího parametru šablony.

GCPtr<T,ReleaseRule = ReleaseDeleteRule,GCImpl = GCDefault>

Třída GCImpl představuje funktor, který (po instanciování objektu) vrací ukazatel na IGCService. Výchozí hodnota je GCDefault, což znamená aktuálně globálně nastavený GC. Toto nastavení lze změnit jak při kompilaci, tak při běhu. Při kompilaci můžeme toto výchozí nastavení změnit prostě tak, že specifikujeme jiný funktor. Za běhu lze globální instanci měnit prostřednictvím třídy GCDefault, která obsahuje funkci setGlobalService. Knihovna nabízí tři varianty.

GCSingleThread
Použijte v jednovláknových aplikacích, nebo tam, kde volání funkcí rozhraní IGCService jsou vždy serializovaná.
GCMultiThread
Použijte ve vícevláknových aplikacích, kde hrozí, že vlákna budou IGCService volat nahodile a je třeba přístup serializovat. Tato varianta interně všechny přístupy serializuje. Počas provádění collect() mohou být alokace dočasně pozastaveny, což v konečném důsledku může zastavit všechna vlákna.
GCExtraThread
Vyhrazuje pro GC speciální vlákno, které zpracovává jednotlivé požadavky paralelně. Jeho použití je vhodné tam, kde alokace musí být rychlé a jakékoliv zastavení při collect() je nežádoucí. Zároveň ale nesmí být alokace časté, nebo musím pro požadavky vyhradit dostatečně dlouhou frontu. Při přeplnění fronty totiž stejně dojde k zastavení vláken do doby, než GC aspoň část fronty nevyřídí.

V třetí variantě je krásně demonstrováno, jak je vlastní GC oddělen od alokací. Nemusí jednotlivé požadavky vyřizovat hned, ale klidně opožděně. Objekty, které jsou opuštěni se stejně dříve, či později uvolní a nezáleží na tom, zda je to dáno tím, že collect probíhá jen občas, nebo že fronta požadavků je dlouhá. Pouze vynucený collect má tu vlastnost, že nejprve počká na vyprázdnění fronty a pak se vykoná.

GC a STL

Při implementaci GC byl kladen požadavek na podporu STL kontejnerů. Bez podpory v STL by reference uložené např do vektoru byly považovány za externí a v případě vzniku cyklické reference (do vektoru uložená reference na objekt, který obsahuje referenci na tento vektor) by došlo k memory-leaku. To proto, že GC nic neví o paměti, ve které STL kontejner drží jednotlivé prvky.

Pro správnou funkci GC je třeba upravit alokátor tak, aby při jakékoliv alokaci paměti uvnitř nějakého kontejneru GC dokázal tuto paměť spojit s existujícím objektem již spravovaným v GC. Pak reference vytvořené uvnitř této paměti jsou automaticky podpořeny referencí vektoru, pokud ten je podpořen nějakou další referencí, která je nakonec podpořena externí referencí.

Tento alokátor je uložen v souboru gcstl.h. Jmenuje se GCAllocator a je to pochopitelně šablona

template<class T, class GCImpl = GCDefault, template <class> class BaseAllocator = std::allocator>
class GCAllocator: public BaseAllocator<T>;

Alokátor kromě typu, který alokuje (typ T), obsahuje také jmeno funktoru provádějící implementaci GC (zde tradičně GCDefault). Parametrem BaseAllocator lze změnit výchozí alokátor, který tento alokátor dědí. Výchozí hodnota specifikuje, že dědit se bude std::allocator. To umožňuje většinu operací ponechat původnímu (výchozímu) STL alokátoru, a jen nejnutnější operace alokace a dealokace fyzické paměti kontrolovat a případně hlásit na rozhraní GC.

K propojení nějakého bloku paměti s existujícím objektem slouží metody GC<>::linkMemory a GC<>::unlinkMemory. Funkce registrují paměťový blok stejně jako registerObject a provádí slinkování bloku s vlastníkem (jenž je jedním z parametrů dané funkce). Je povoleno, aby vlastník měl víc paměťových bloků (množství není omezeno). Stejně tak je ale povoleno, aby jeden paměťový blok měl více vlastníků. Je třeba akorát zajistit správné odpojení bloku. GC totiž tyto bloky sám neuvolní, spoléhá totiž na vlastníka, že sám nejlépe pozná okamžik uvolnění, což je ideální právě pro allokátor v STL.

Použití alokátoru demonstrujeme na std::vector

std::vector<MujObjekt,GCAllocator<MujObjekt> > mojePole;

Na konec dodejme, že používání alokátoru má smysl jen tehdy, pokud objekt vkládany do kontejneru obsahuje objekty typu GCPtr, nebo při ukládání samotných GCPtr. Pokud kontejner nemá takovou vlastnost, je zbytečné allokátor používat i přesto, že je spravován pomocí GC. Jeho obsah v tomto případě nemá pro GC žádný význam.

To je zatím vše

Třeba časem něco dodám. Enjoy

vytvořeno: 27.1.2009 01:03:00, změněno: 27.1.2009 01:03:00
Jsou informace v článku pro Vás užitečné?
  • (3)
  • (0)
  • (0)
  • (0)
  • (1)
Nick nebo OpenID
Vzkaz
 
25.7.2016 18:49:32

zFRBYOXDSv9

Cisco tests aren&#8217;t simple to complete, plus the preliminary Cisco qualifications, the particular CCNA, requirements hands-on expertise as well as knowledge.? power to counteract undesired software from installing thv,lesvesmleeel of quality and operation).
13.9.2015 10:23:44

conversę

Ь converse
conversę
11.8.2015 02:59:33

new balance

new balancę
10.8.2015 06:10:50

converse

converse taiwan
converse
28.7.2015 16:41:00

new balance

http://www.5866.com.tw/image/nbstore.php
new balance
23.7.2015 17:59:04

new balance

new balanceЬ
16.7.2015 13:06:25

new balance
30.6.2015 02:20:32

toms singapore shoes

toms shoes online singapore
toms singapore shoes http://tomssg.mennosource.org/

Podobné články

Garbage collector - automatický úklid objektů v C++

Článek navazuje na GarbageCollector v C++, prototyp, kde jsme si představili prototyp třídy provádějící automatický úklid objektů známy jako "garbage collector". Dnes si ukážeme další verzi, nazvěme ji "alfa"

GarbageCollector v C++, prototyp

Neustále slýchám, jak v C++ chybí Garbage Collector. Ač si o tomto nástroji myslím své, a v zásadě jsem proti zavedení GC v C++, přijal jsem tyto nářky jako výzvu.

Automatické klonování objektů v C++ II

Dneska se podíváme jak klonovat pomocí clone_ptr (ClonePtr)

Jak sdílet prostředky (resources) v C++

Nemnohokrát jsem řešil v programech psaných v C++, jak sdílet prostředky (resources), jinými slovy, jak zajistit, že jedinečné prostředky budou uvolňovány až v okamžiku, kdy je nikdo nepotřebuje. Podívejme se na jednoduché řešení.

Šablony: Nastavování vlastností tříd

Svět šablon v C++ je velmi zajímavý, pro znalce dokonalý, pro začátečníky šílený a nepřehledný. Následující článek pojednává o dalším kouzlu se šablonami
Reklama: