it-swarm-eu.dev

Proč Garbage Collection, pokud existují inteligentní ukazatele

V těchto dnech je shromážděno tolik jazyků. Je dokonce k dispozici pro C++ třetími stranami. Ale C++ má RAII a inteligentní ukazatele. Jaký je tedy smysl použít sběr odpadu? Dělá to něco navíc?

A v jiných jazycích, jako je C #, budou-li se všemi odkazy zacházeno jako s inteligentními ukazateli (s ponecháním RAII stranou), podle specifikace a implementace, bude stále potřeba sběratelů odpadu? Pokud ne, tak proč tomu tak není?

69
Gulshan

Jaký je tedy účel využití odpadků?

Předpokládám, že máte na mysli inteligentní ukazatele s počítáním referenčních hodnot a všimnu si, že jsou (základní) formou sběru odpadků, takže odpovím na otázku "Jaké jsou výhody jiných forem sběru odpadků v porovnání s inteligentními ukazateli s počítáním referenčních hodnot". namísto.

  • Přesnost. Samotné počítání referencí propouští cykly, takže inteligentní ukazatele s počítáním referencí budou obecně unikat paměti, ledaže by byly přidány jiné techniky k zachycení cyklů. Po přidání těchto technik zmizel přínos jednoduchého počítání referencí. Také si všimněte, že referenční referenční počítání a sledování GC sbírají hodnoty v různých časech, někdy se počítání referencí shromažďuje dříve a někdy trasovací GC se shromažďují dříve.

  • propustnost. Inteligentní ukazatele jsou jednou z nejméně účinných forem sběru odpadu, zejména v souvislosti s aplikacemi s více vlákny, když jsou referenční počty atomově naraženy. Pro zmírnění tohoto problému existují pokročilé techniky počítání referencí, ale trasovací GC jsou v produkčních prostředích stále algoritmem výběru.

  • latence. Typické implementace inteligentních ukazatelů umožňují destruktorům lavinu, což vede k neomezeným časům pauzy. Jiné formy sběru odpadu jsou mnohem přírůstkové a mohou být dokonce v reálném čase, např. Pekařský běžecký pás.

71
Jon Harrop

Protože se na to nikdo nedíval z tohoto úhlu, přeformuluji vaši otázku: proč dát něco do jazyka, pokud to dokážete v knihovně? Ignorování specifických implementací a syntaktických podrobností, GC/smart ukazatele jsou v podstatě zvláštním případem této otázky. Proč definovat sběratele odpadu v samotném jazyce, pokud jej můžete implementovat do knihovny?

Na tuto otázku existuje několik odpovědí. Nejdůležitější první:

  1. Zajistíte, aby jej celý kód mohl použít k interoperabilitě. To je, myslím, velký důvod, proč je kód znovu použit a kód sdílení se opravdu nezačalo, dokud Java/C #/Python/Ruby. Knihovny musí komunikovat a jediným spolehlivým sdíleným jazykem, který mají, je to, co je v samotné jazykové specifikaci (a do jisté míry i její standardní knihovna). Pokud jste se někdy pokusili znovu použít knihovny v C++, pravděpodobně jste zažili strašlivou bolest, kterou žádná standardní sémantika paměti nezpůsobuje. Chci předat strukturu nějaké lib. Předám odkaz? Ukazatel? scoped_ptr? smart_ptr? Předávám vlastnictví, nebo ne? Existuje způsob, jak to naznačit? Co když lib potřebuje alokovat? Musím mu přidělit alokátor? Tím, že se správa paměti nestane součástí jazyka, nutí každou dvojici knihoven, aby zde musely vyjednat svou vlastní konkrétní strategii, a je opravdu těžké přimět všechny, aby souhlasily. GC to dělá naprosto bez problému.

  2. Můžete vytvořit syntaxi kolem ní. Protože C++ nezahrnuje samotnou správu paměti, musí poskytnout řadu syntaktických háčků, aby kód na úrovni uživatele mohl vyjádřit všechny podrobnosti. Máte ukazatele, reference, const, dereferenční operátory, indukční operátory, adresu atd. Pokud převedete správu paměti do samotného jazyka, může být kolem toho navržena syntaxe. Všichni tito operátoři zmizí a jazyk bude čistší a jednodušší.

  3. Dostanete vysokou návratnost investice. Hodnota, kterou vygeneruje daný kus kódu, se vynásobí počtem lidí, kteří jej používají. To znamená, že čím více uživatelů máte, tím více si můžete dovolit utratit za kus softwaru. Když přesunete objekt do jazyka, budou jej používat všichni uživatelé jazyka. To znamená, že můžete na ni vyčlenit více úsilí než na knihovnu, kterou používá pouze podmnožina těchto uživatelů. To je důvod, proč jazyky jako Java a C # mají absolutně prvotřídní virtuální počítače a fantasticky vysoce kvalitní sběratele odpadu: náklady na jejich vývoj jsou amortizovány milionům uživatelů.

66
munificent

Odpadky v podstatě znamená, že přidělené objekty jsou automaticky uvolněny v určitém okamžiku poté, co už nejsou dosažitelné.

Přesněji, jsou uvolněny, když se stanou nedosažitelnými pro program, protože kruhově odkazované objekty by se nikdy neuvolnily jinak.

Inteligentní ukazatele pouze odkazují na jakoukoli strukturu, která se chová jako obyčejný ukazatel, ale má připojené některé další funkce. Tyto include, ale nejsou omezeny pouze na deallocation, ale také na copy-on-write, vázané kontroly, ...

Nyní, jak jste uvedli, inteligentní ukazatele lze použít k implementaci formy sběru odpadu.

Ale myšlenkový směr jde následujícím způsobem:

  1. Sběr odpadu je skvělá věc, protože je to pohodlné a já se musím starat o méně věcí
  2. Proto: Chci sbírat odpadky v mém jazyce
  3. Jak se nyní může GC dostat do mého jazyka?

Samozřejmě to můžete navrhnout od začátku. C # byl navržen tak, aby byl sbírán odpadky, takže stačí new váš objekt a bude uvolněn, když odkazy spadnou mimo rozsah. Jak se to dělá, záleží na kompilátoru.

Ale v C++ neexistovala žádná úmyslná kolekce odpadu. Pokud přidělíme nějaký ukazatel int* p = new int; A vypadne to mimo rozsah, p sám se odstraní ze zásobníku, ale nikdo se nestará o přidělenou paměť.

Nyní jediné, co máte od začátku, jsou deterministické destruktory . Když objekt opustí obor, ve kterém byl vytvořen, je vyvolán jeho destruktor. V kombinaci se šablonami a přetížením operátorem můžete navrhnout objekt wrapper, který se chová jako ukazatel, ale k likvidaci prostředků k němu připojených (RAII) používá funkci destruktoru. Tomu říkáte inteligentní ukazatel .

To vše je velmi specifické pro C++: Přetížení operátora, šablony, destruktory, ... V této konkrétní jazykové situaci jste vyvinuli inteligentní ukazatele, které vám poskytnou požadované GC.

Pokud ale navrhujete jazyk s GC od začátku, jedná se pouze o detail implementace. Říkáte, že objekt bude vyčištěn a kompilátor to udělá za vás.

Inteligentní ukazatele jako v C++ by pravděpodobně nebyly možné ani v jazycích jako C #, které nemají vůbec žádnou deterministickou destrukci (C # to obchází tím, že poskytuje syntaktický cukr pro volání .Dispose() na určitých objektech). Neveřejné zdroje budou konečně získány GC, ale nedefinováno, kdy přesně se to stane.

A to zase může GC umožnit, aby svou práci vykonával efektivněji. NET GC může být zabudována hlouběji do jazyka než inteligentní ukazatele, které jsou na něm umístěny. zpožďovat operace s pamětí a provádět je v blocích, aby byly levnější nebo dokonce pohybovat paměť kolem pro zvýšení efektivity na základě toho, jak často jsou objekty přístupné.

36
Dario

Podle mého názoru existují dva velké rozdíly mezi sběrem odpadků a inteligentními ukazateli používanými pro správu paměti:

  1. Inteligentní ukazatele nemohou sbírat cyklické odpadky; sběr odpadků může
  2. Inteligentní ukazatele provádějí veškerou práci v okamžiku, kdy se na vláknu aplikace odkazují, dereferencí a deallokací; sběr odpadků nemusí

První znamená, že GC bude shromažďovat odpadky, které inteligentní ukazatele nebudou; Pokud používáte inteligentní ukazatele, musíte se vyhnout vytváření tohoto druhu odpadu nebo být připraveni se s ním vypořádat ručně.

To znamená, že bez ohledu na to, jak inteligentní inteligentní ukazatele jsou, jejich provoz zpomalí pracovní vlákna v programu. Sběr odpadu může odložit práci a přesunout ji do jiných vláken; což umožňuje celkově vyšší efektivitu (provozní náklady moderního GC jsou nižší než běžný systém malloc/free, a to i bez dodatečných režijních nákladů na inteligentní ukazatele) a dělají, co ještě musí udělat, aniž by se dostali do způsob aplikačních vláken.

Nyní si uvědomte, že inteligentní ukazatele, které jsou programovými konstrukcemi, lze použít k provádění nejrůznějších dalších zajímavých věcí - viz Darioova odpověď - které jsou zcela mimo rozsah sběru odpadu. Pokud je chcete udělat, budete potřebovat inteligentní ukazatele.

Pro účely správy paměti však nevidím žádné vyhlídky na inteligentní ukazatele nahrazující sběr odpadu. Prostě na to nejsou tak dobří.

4
Tom Anderson

Termín odvoz odpadků znamená, že existuje nějaký odpad. V C++ inteligentní ukazatele přicházejí v několika příchutích, především v jedinečném_ptr. Unikátní_ptr je v podstatě jediným vlastnictvím a konstrukcí rozsahu. V dobře navržené části kódu by většina haldy přidělených věcí normálně sídlila za inteligentními ukazateli unique_ptr a vlastnictví těchto zdrojů bude vždy dobře definováno. Sotva existuje žádná režie v unique_ptr a unique_ptr odstraní většinu problémů s manuální správou paměti, která tradičně přiváděla lidi ke spravovaným jazykům. Nyní, když se více jader běžících souběžně stává stále běžnějším, jsou pro návrh důležitější principy návrhu, které řídí kód k použití jedinečného a přesně definovaného vlastnictví v kterémkoli okamžiku. Použití výpočtového modelu herce umožňuje konstrukci programů s minimálním množstvím sdíleného stavu mezi vlákny a jedinečné vlastnictví hraje hlavní roli při zajišťování toho, aby vysoce výkonné systémy efektivně využívaly mnoho jader bez režie sdílených mezi- data vláken a předpokládané požadavky na mutex.

Dokonce i v dobře navrženém programu, zejména ve vícevláknových prostředích, nelze vše vyjádřit bez sdílených datových struktur a pro ty datové struktury, které skutečně vyžadují, musí vlákna komunikovat. RAII v c ++ funguje docela dobře pro celoživotní záležitosti v nastavení s jedním vláknem, v nastavení s více vlákny nemusí být životnost objektů zcela hierarchicky definována. Pro tyto situace nabízí použití shared_ptr velkou část řešení. Vytvoříte sdílené vlastnictví zdroje, a to v C++ je jediné místo, kde vidíme odpadky, ale v tak malých množstvích, že řádně navržený program c ++ by měl být považován spíše za implementaci kolekce „vrhů“ se sdílenými-ptr než s plnohodnotnou sbírkou odpadků jako implementováno v jiných jazycích. C++ prostě nemá tolik „odpadků“, které by bylo možné sbírat.

Jak uvedli ostatní, inteligentní ukazatele počítané podle referencí jsou jednou z forem odvozu odpadků a pro to má jeden hlavní problém. Příkladem, který se používá většinou jako nevýhoda referenčních počítaných forem sběru odpadu, je problém s vytvářením osamocených datových struktur spojených s inteligentními ukazateli k sobě navzájem, které vytvářejí klastry objektů, které se navzájem brání v shromažďování. Zatímco v programu navrženém podle hereckého modelu výpočtu, datové struktury obvykle neumožňují vznik takových nespojitelných klastrů v C++, když použijete přístup sdílených dat k vícevláknovému programování, jak se používá převážně ve velké části z tohoto odvětví se tyto osamocené klastry rychle stanou skutečností.

Abych to shrnul všechno, pokud máte na mysli použití sdíleného ukazatele, máte na mysli široké použití unique_ptr v kombinaci s hereckým modelem výpočtu přístupu pro vícevláknové programování a omezené použití shared_ptr, než jiné formy odvozu odpadků vám nic nekoupí přidané výhody. Pokud by vám však přístup sdíleného všeho skončil na místě s sdíleným_ptrem, měli byste zvážit přepnutí souběžných modelů nebo přechod na spravovaný jazyk, který je více zaměřen na širší sdílení vlastnictví a souběžný přístup k datovým strukturám.

4
user1703394

Většina inteligentních ukazatelů je implementována pomocí počítání referencí. To znamená, že každý inteligentní ukazatel, který odkazuje na objekt, zvyšuje počet odkazů na objekty. Když tento počet klesne na nulu, objekt je uvolněn.

Problém je, pokud máte kruhové reference. To znamená, že A má odkaz na B, B má odkaz na C a C má odkaz na A. Pokud používáte inteligentní ukazatele, pak, abyste uvolnili paměť spojenou s A, B & C, musíte ručně dostat se tam "přerušit" kruhový odkaz (např. pomocí weak_ptr v C++).

Sběr odpadu (obvykle) funguje docela jinak. Většina sběratelů odpadu v těchto dnech používá test dosažitelnosti. To znamená, že se dívá na všechny odkazy na zásobníku a ty, které jsou globálně přístupné, a pak sleduje každý objekt, na který tyto odkazy odkazují, a objekty oni odkazují na atd. Všechno ostatní je odpadky. .

Takto nezáleží na kruhových referencích - pokud ani A, B a C nejsou dosažitelné, lze paměť získat zpět.

Existují další výhody „skutečné“ sbírky odpadu. Například přidělení paměti je velmi levné: stačí zvýšit ukazatel na „konec“ bloku paměti. Deallocation má také konstantní amortizovanou cenu. Ale jazyky jako C++ vám samozřejmě umožňují implementovat správu paměti téměř libovolným způsobem, takže můžete přijít s alokační strategií, která je ještě rychlejší.

Samozřejmě v C++ je množství haldy alokované paměti obvykle menší než referenční jazyk, jako je C # /. NET. To ale ve skutečnosti není problém s odpady a inteligentními ukazateli.

V žádném případě není problém ten správný než ten druhý. Každý z nich má výhody a nevýhody.

2
Dean Harding

Jedná se o výkon . Nepřidělení paměti vyžaduje hodně administrace. Pokud je uvolnění spuštěno na pozadí, zvyšuje se výkon popředí. Bohužel, přidělení paměti nemůže být líné (přidělené objekty budou použity ve svatém příštím okamžiku), ale uvolnění objektů může.

Zkuste v C++ (bez jakéhokoli GC) alokovat velkou skupinu objektů, vytisknout „ahoj“ a poté je vymazat. Budete překvapeni, jak dlouho trvá uvolnění předmětů.

Také GNU libc) poskytuje účinnější nástroje pro nepřidělení paměti, viz překážky . Musíte si všimnout, že s překážkami nemám žádné zkušenosti, nikdy jsem je nepoužil.

2
ern0

Sběr odpadu může být efektivnější - v zásadě „seskupuje“ režii správy paměti a dělá to najednou. Obecně to bude mít za následek méně celkové CPU vynakládané na delokaci paměti, ale to znamená, že budete mít v určitém bodě velkou aktivitu delokace. Pokud GC není správně navržen, může to být pro uživatele viditelné jako „pauza“, zatímco GC se snaží uvolnit paměť. Většina moderních GC je velmi dobrá v udržení neviditelnosti pro uživatele s výjimkou těch nejnepříznivějších podmínek.

Inteligentní ukazatele (nebo jakékoli schéma počítání referencí) mají tu výhodu, že k nim dojde přesně tehdy, když byste očekávali, že se podíváte na kód (inteligentní ukazatel je mimo rozsah, věc bude odstraněna). Zde a tam získáte malé dávky delokace. Celkově můžete na delokaci využít více času procesoru, ale protože je rozprostřen ve všech věcech, které se odehrávají ve vašem programu, je pro uživatele méně pravděpodobné (vyloučení alokace nějaké datové struktury monster).

Pokud děláte něco, na čem záleží na reaktivitě, navrhl bych, aby vám inteligentní ukazatele/počítání referencí přesně věděly, kdy se něco děje, takže můžete při kódování vědět, co se pravděpodobně stane viditelným pro vaše uživatele. V nastavení GC máte jen nejzákladnější kontrolu nad sběratelem odpadků a jednoduše se musíte pokusit tuto věc obejít.

Na druhou stranu, pokud je vaším cílem celková propustnost, může být systém založený na GC mnohem lepší volbou, protože minimalizuje zdroje potřebné pro správu paměti.

Cykly: Problém cyklů nepovažuji za významný. V systému, kde máte inteligentní ukazatele, máte tendenci směřovat k datovým strukturám, které nemají cykly, nebo si prostě dáváte pozor, jak takové věci pustíte. V případě potřeby lze použít objekty správce, které vědí, jak přerušit cykly ve vlastnictví objektů, aby se automaticky zajistilo správné zničení. V některých oblastech programování to může být důležité, ale pro většinu každodenní práce je to irelevantní.

2
Michael Kohne

Jedním z omezení inteligentních ukazatelů je to, že ne vždy pomáhají proti kruhovým referencím. Například máte objekt A ukládající inteligentní ukazatel na objekt B a objekt B ukládá inteligentní ukazatel na objekt A. Pokud zůstanou pohromadě bez resetování některého z ukazatelů, nebudou nikdy přiděleny.

To se děje proto, že inteligentní ukazatel musí provést specifickou akci, která nebude ve výše uvedeném scénáři trojnásobná, protože oba objekty jsou pro program nedostupné. Sběr odpadu se bude vypořádat - bude správně identifikovat, že objekty nejsou znovu připojitelné k programu a budou shromažďovány.

1
sharptooth

Je to spektrum.

Pokud nemáte omezené výkony a jste připraveni dát Grind dovnitř, skončíte v Shromáždění nebo C, se všemi břemeny na vás, abyste učinili správná rozhodnutí a všechnu svobodu dělat to, ale s tím , veškerá svoboda to zkazit:

„Řeknu vám, co máte dělat, to udělejte. Věřte mi“.

Sběr odpadu je druhým koncem spektra. Máte velmi malou kontrolu, ale postará se o vás:

"Řeknu ti, co chci, aby se to stalo.".

To má spoustu výhod, většinou to, že nemusíte být stejně důvěryhodní, pokud jde o přesné poznání, kdy zdroj již není zapotřebí, ale (i přes některé odpovědi zde plovoucí) není dobrý výkon, a předvídatelnost výkonu. (Stejně jako všechny věci, pokud máte kontrolu, a děláte něco hloupého, můžete mít horší výsledky. Nicméně naznačit, že vědět, v jaké době jsou kompilace, jaké jsou podmínky pro to, aby bylo možné uvolnit paměť, nelze použít jako výherní výkon nad naivní).

RAII, určování rozsahu, počítání ref, atd. jsou všichni pomocníci, kteří vám umožní pohybovat se dále v tomto spektru, ale není to tam úplně. Všechny tyto věci stále vyžadují aktivní použití. Stále nechávají a vyžadují, abyste komunikovali se správou paměti tak, jak to sbírka odpadků ne.

1
drjpizzle

Nezapomeňte, že nakonec se vše scvrkne na prováděcí pokyny CPU. Pokud je mi známo, všechny procesory na spotřebitelské úrovni mají instrukční sady, které vyžadují, abyste měli data uložená na daném místě v paměti a měli jste ukazatele na uvedená data. To je vše, co máte na základní úrovni.

Všechno navíc se sběrem odpadků, odkazy na data, která mohla být přemístěna, zhutnění haldy atd. Atd., Vykonává práci v rámci omezení daných výše uvedeným paradigmatem "část paměti s ukazatelem adresy". Stejná věc jako u inteligentních ukazatelů - MUSÍTE NUTNĚ spustit kód na skutečném hardwaru.

0
user1249