Do jaké míry je počítání referencí v Pythonu optimalizované? Mají tam sofistikované optimalizace ve spojení s CoW (ve stylu swiftího isKnownUniquelyReferenced) a statickou analýzu eliminující nadbytečné aktualizace čítače referencí (jako v LLVM)? Například OCaml chce vylepšit (zefektivnit) správu paměti, proto plánují opustit tracing GC. Podobné trendy v Pythonu?
22. 12. 2020, 13:00 editováno autorem komentáře
No prakticky žádné optimalizace se v klasickém CPythonu neprovádí. Nemáme ani obdobu TLAB ani paralelní GC ve stylu Shenandoah. Osobně by se mi líbílo mít možnost GC kompletně vypnout, a to včetně počítání referencí (to je ovšem řešeno makry, takže asi jen rekompilací Pythonu) - pro krátkou dobu běžící skripty by mi velký heap ani nevadil (popravdě tak velký nebývá - tisíce objektů max typicky, méně než u JVM při bootu).
(na druhou stranu se alespoň vyřešilo uvolňování objektů s redeklarovanou metodou __del__).
S tím opuštěním tracing GC mě to tedy překvapuje. Osobně si myslím, že správa paměti by neměla zpomalovat výpočetní vlákna, takže počitadla referencí jsou z tohoto pohledu dost zlo (zámky atd.). A zrovna rozumně navržený vícegenerační tracing GC je ověřené řešení. Zajímavé by možná byla nějaká detekce, jestli jsou reference z jednoho vlákna a ty atomické čítače nahradit normálními čítači (to by mohlo vyhovovat třeba pro 99% případů a ten zbytek by holt byl lockovaný).
Na AMD64 a ARM64 atomické aktualizace nejsou pomalejší (historicky to býval problém na MIPSu nebo PowerPC). Nicméně hlavní přínos moderních přístupů k RC je to, že statická analýza eleminuje většinu aktualizací úplně (protože nejsou potřebné) a hodně alokací se děje na zásobníku (díky escape analýze, to se používá i pro tracing GC, například v Go to hodně pomáhá). Čítač referencí dříve obnášel paměťovou režii, ale moderní 64-bitové architektury k tomu využívají tagged pointers, takže všechny (domnělé i skutečné) nevýhody a výhrady k RC na moderních CPU padají. Kdysi jsem si ze zvědavosti porovnal historii aktualizací čítače referencí v původním ObjC (z časů OpenStepu) a moderního ARC, statická analýza vyhodí 90% aktualizací a escape analýza flákne více jak polovinu objektů na zásobník (protože jsou "short-lived" a nemusí se pro ně alokovat místo na haldě), takže i kdyby ty zámky byly pomalé, tak jich moc nebude. Podobně dopadlo porovnání GC v Go a Javě, prvně jmenované cpe vše, co se dá, na zásobník, takže samotný GC nemá moc práce. K tomu ARC byly pěkné přednášky na WWDC před několika lety. Apple si prostě experimentálně ověřil, že počítání referencí je efektivnější, a teď k tomu došli i tvůrci OCamlu (někde na Leopardu měl OS X - tehdejší název macOS - i tracing GC pro aplikace nad Cocoa, Apple jej používal ve svých aplikacích, ale s příchodem iPhonu, který měl nejprve jen 128 MB RAM, tracing GC zahodili a vrátili se k RC).
23. 12. 2020, 00:35 editováno autorem komentáře
V CPythonu prakticky žádná větší escape analýza není. Existuje jen peephole optimizer transformující bajtkód a potom https://fatoptimizer.readthedocs.io/en/latest/ s jiným zaměřením.
Ovšem PyPy už je na tom mnohem lépe, jak díky optimalizacím nad AST, tak i kvůli jinak pojatému GC (https://rpython.readthedocs.io/en/latest/garbage_collection.html#garbage-collection), který vlastně pracuje na principu plug&play (GC lze vyměnit, to u CPythonu nejde).
PS: snad si najdu čas o PyPy napsat článek z pohledu použitých technologií.