Hmm, ze by navazali na vyvoj Midori? http://joeduffyblog.com/2015/11/03/blogging-about-midori/
Tvrdit, že počítání referencí je pomalé, je jako tvrdit, že kůň je rychlejší než auto, protože NW Präsident jezdil v roce 1897 pomalu. Je to naopak nejefektivnější známý způsob automatické správy paměti. Naivní implementace se změnou čítače a zamykáním při každém přiřazení je relativně pomalá, ale takto by to v roce 2019 implementoval jen blb. Dnešní sofistikované postupy psaní překladačů a runtimů nejsou raketová věda, každý aspoň průměrně inteligentní člověk se zájmem o programovací jazyky se může snadno dovzdělat i samostudiem.
class A { A * p; A() {p = this;}};
while(true) { new A(); }
Na tomto programu RC alokátoru dojde paměť, tracing GC to v O(1) vyhodnotí jako volnou paměť.
Můžeš argumentovat, že to optimalizátor prokoukne, v tom případě tě mohu ujistit, že na každý optimalizátor lze najít algoritmus, kde to selže. A samozřejmě čím složitější data a algoritmus, tím hůř se to bude programátorovi hlídat (a hlídat to musí vždy).
Dělat moderní jazyk s počítáním referencí je naopak velmi rozumné. Refcounting a tracing mají výrazně odlišné vlastnosti. Tracing GC není lepší, jen má pasti jinde. Je specializovaný na uklízení paměti v nerealtime prostředí. Na úklid socketů, handlů a podobných věcí se nehodí. A schovat kus cyklu tak, aby ho GC neviděl, taky není zas tak obtížné.
Pokud si mám jako programátor volit mezi řešením nízkoúrovňových věcí jako jsou cykly, nebo nízkoúrovňových věcí jako ruční úklid všeho kromě paměti, tak osobně volím ty cykly. Jestli převáží jedny nebo druhé zádrhele dost záleží na kontextu a prostor bude IMO pro oba typy jazyků.
Tak on tracing GC ve většině případů stačí a když má nízkou latenci (jako v Go), nemá výrazně negativní vliv na nic kromě nejvyšší úrovně alokované paměti (v odborných článcích se tomu říká “high water mark”), což nevadí na serveru ani v GUI aplikacích, jen v případě realtime aplikací a na embedded.
Na druhou stranu v případě RC GC vznikají ref. cykly jen u některých datových struktur, jenže příslušné knihovny píšou typicky programátoři na úrovni, kteří si to umí ohlídat levou zadní. Když pak nějaký méně bystrý programátor takovou knihovnu používá, už ho to netrápí. Moc hezká diskuse k tématu je někde na diskusním fóru ke Swiftu, kde autoři podrobně vysvětlují na dostatečně technické úrovni, proč je RC efektivnější co do rychlosti i nároků na paměť. Účastníci diskuse zastávající se tracing GC se pak rozdělí do dvou skupin: 1) pochopí, jak fungují příslušné sofistikované optimalizace překladače, 2) bliss in ignorance.
Já mysím, že to myslel trochu jinak. Jde o to, jaká úroveň je potřeba. Můj dojem je, že pohlídat si cykly je jedna z těch nejjednodušších věcí, co se ode mě žádá. Promyslet si, kdo co vlastní, mi nepřijde jako extra nápor na bednu.
Nepamatuju si situaci že by mi refcountované objekty leakovaly kvůli cyklům.
No ještě trochu jinak jsem to myslel já. Když navrhnu data a algoritmus, tak tam žádný koncept vlastnictví není potřeba. Je to nutné vymyslet až ve chvíli, kdy se to má implementovat v jazyce, který bez toho nedovede spravovat paměť. Ale programátoři jsou tak vycvičení, že si tento posun ani neuvědomují a automaticky a skrytě ten trasovací GC zakomponují do svého řešení. U jednoduchých problémů se to tak neprojeví, ale já se dost zabývám grafovými úlohami a tam to jsem nucen řešit pořád dokola.
„...v případě RC GC vznikají ref. cykly jen u některých datových struktur...“
Které to jsou??? Mě bez velkého přemýšlení napadá cyklus u namodelování rodinných vztahů mezi osobami, podobných modelů bude mraky.
„...příslušné knihovny píšou typicky programátoři na úrovni, kteří si to umí ohlídat levou zadní...“
Takže model musí být 1. v knihovně, 2. to vyžaduje úpravu modelu, 3. co když vznikne chyba? Proč by měla neschopnost virtuálního počítače spravovat paměť ovlivňovat model o několik úrovní abstrakce výše???
Je jich mraky. Když vezmeme v úvahu, že data mají většinou stromovou strukturu, tak cyklus snadno vznikne tam, kde při nějakém zpracování uzlu potřebujeme jeho nadřazený uzel (což ani nemusí znamenat vztah vlastnictví). Takže v praxi je to skoro pořád. Kdo nemá k dispozici transparentní GC, tak to samozřejmě udělat nesmí a všelijak to obchází. Nakonec dojde k závěru, že to vlastně není potřeba skoro nikdy, protože si zvykl data i algoritmy pokaždé přiohnout.
SB : Ale kdepak. Modelované vztahy neříkají nic o tom, jestli mají být udělané silnou nebo slabou referencí. To je implementační detail.
Ty modelované objekty málokdy musí existovat ve vzduchoprázdnu aby je musel udržovat při životě jakýkoliv odkaz na ně. Když existují v kontextu nějakého "světa" nebo něco podobného (graf, ...), tak není problém mít řetěz silných referencí, které je udržují naživu. A zbytek významových vazeb můžou být slabé odkazy.
Nevím, jestli tomu rozumím. Každopádně to naprosto běžné používám a není s tím sebemenší problém.
On ten prvotní problém je, že někteří naivové chtějí modelovat svět programovacím jazykem. To mohla být před půl stoletím zajímavá myšlenka v kontextu OOP, jenže se neosvědčila a dnes jde o těžký anachronismus. Lpění na starých nefunkčních myšlenkách a konceptech je znakem ignorantství (v tom lepším případě).
A to zatím nebylo zmíněno, že slabé reference se používají i s tracing GC.
Ta struktura tam stejně bude. I v trasovaných jazycích obvykle nebude např. graf jen ad-hoc hromada objektů pro uzly a hrany, ale bude tam i objekt pro graf, ze kterého bude přístup na všechny uzly a hrany toho grafu.
Trasované jazyky zase neumí jiné věci. Třeba je problém s nedeterministickým úklidem. Sockety, soubory a podobné věci nemůže uklízet GC, protože nesmí zůstat viset. Takže tam bude muset být velmi podobná další struktura, přes kterou se jedním voláním proleze a uklidí celý ten graf.
Vždycky budou potřeba nějaké další struktury, které řeší implementační detaily a to bez ohledu na jazyk nebo prostředí. A to jenom proto že každé VM může umět jen omezenou množinu vzájemně kompatibilních věcí.
> ale bude tam i objekt pro graf, ze kterého bude přístup na všechny uzly a hrany toho grafu.
No právě že tam být nemusí pokud není vyžadován přímo algoritmem. O tom celou dobu mluvím. Tohle jsou právě ty případy, kdy je řešení úlohy ovlivněno potřebou po sobě uklidit, protože nemá trasovací GC a tyto praktiky pak programátoři tahají i tam, kde to není třeba (a ani si toho nevšimnou, že dělají něco zbytečné až škodlivé).
Zdroje jako soubory a sockety lze většinou snadno uvolnit deterministicky. V jazycích jako C++ je to spojeno s uvolňováním paměti, protože se tam uklízí deterministicky vše, ale to neznamená, že by to existence GC nějak zesložiťovala. Ještě jsem nenarazil na situaci, kdy bych se nedeterministickému úklidu musel nějak přizpůsobovat. Snad před 20 lety, když bylo paměti málo a bylo to pomalé.
Ještě si dovolím poznámku - mluvíme o úklidu paměti u trasovacích GC, ale to je poněkud zavádějící pohled. Pro pochopení celého paradigmatu je dobré nahlédnout, že to ve skutečnosti předstírá prostředí, kde je k dispozici nekonečné množství paměti (jak je toho dosaženo lze brát jako implementační detail). V takovém prostředí se nutně zjednoduší celý návrh dat a algoritmů, protože s nekonečným množstvím paměti odpadá spousta starostí :-) Dnešním GC takový přístup navíc mnohem lépe "chutná". Pokud se narazí na problémy s výkonem, tak to lze řešit jako jakoukoli jinou optimalizaci (čili ne slepě a předčasně).
> Ještě jsem nenarazil na situaci, kdy bych se nedeterministickému úklidu musel nějak přizpůsobovat. Snad před 20 lety, když bylo paměti málo a bylo to pomalé.
Ono nejde až tak o uvolňování paměti. Uzavření socketu nebo souboru je uvolnění jiného zdroje a v neposlední řadě má typicky i další dopady. Nebylo by dobré nechat otevřený socket nebo soubor, jen proto, že mám dost paměti. U zámku (který na Windows k otevřeným souborům dostaneme automaticky) je to ještě zřejmější.
V neposlední řadě tu jsou případy, které sama o sobě neřeší ani deterministická dealokace. Mějme nějaký datový model a nějaké GUIčko, které si v datovém modelu zaregistruje listenery. GUIčko ukončíme (může jít jen o zavření jednoho okna). Pokud si to nepohlídáme, zůstanou nám listenery v modelu, a tím i samotné GUIčko v paměti. Co hůř, nemáme jen nepořádek v paměti, ale s každým updatem modelu updatujeme nepotřebnou instanci GUIčka, což nás stojí čas CPU. Lze to sice „vyřešit“ pomocí slabých referencí, ale to je jen částečné řešení. Bude-li dost paměti, může tento nepořádek zabíjet CPU ještě dlouho před tím, než zaberou slabé reference. Jednoduše, garbage collectory jsou určeny k tomu, aby řešily paměť, ale těžko mohou počítat s dalšími dopady, jako je dopad na CPU. Dokonce to může s trochou heuristik fungovat naopak – můžeme se snažit GC spouštět jen v případě nízké zátěže CPU (samozřejmě někdy to nevyjde), pak to bude zátěž CPU jen oddalovat.