ětšina zpomalení je způsobena patrně zvětšením binárek a agresivním vkládání funkcí (function inline), což zvětšuje tlak na registry a vyrovnávací paměť.
A jak si autor představuje, že by "tlak na registry" mohl mít vliv na zpomalení programu? I když bude program menší, tak "tlak na registry" bude pořád stejný. Každá dnešní moderní architektura používá register renaming, takže "tlakem" to asi nebude...
Vysvětlení je přitom mnohem prostší - co způsobuje tak masivní zvětšení binárky? Je to inlining funkcí, jak je psané v článku, a nebo spíš loop unrolling? Ono totiž když compiler unrolluje všechny loopy v programu, které třeba běžně mají jen pár iterací, tak tam je právě problém - kód bude celkově větší (unrolling znamená extra prolog/epilog - "tlak" právě na instruction cache) a aby ten unrolling dával smysl, tak se musí amortizovat počtem iterací. Takže pokud compiler běžně unrollne loopy, kde je počet iterací minimální, tak to stojí jak strojový čas tak i extra instruction cache.
To ale musí všechno spočítat překladač. Pokud loop potřebuje 8 proměnných a existuje jen 16 registrů, tak moc nemá cenu pokoušet se o unrolling, pokud se nejedná o nějakou autovektorizaci třeba, kde se použijou jiné registry (SIMD).
Ale překladače v tomto opravdu chybují, a problém je, že loop unrolling se dělá mnohem dřív než alokace registrů, takže se stává, že před transformací je program "perfektní", ale po transformaci už chybí registry. Každý unroll stojí i nějaké GP registry, protože prolog/epilog (popř. lead/tail loop) potřebuje většinou vlastní counter, popř. pokud se dělá alignment tak jsou potřeba nějaké dočasné registry, které nemusí být k dispozici a nebo sice jsou k dispozici, ale funkce bude potřebovat větší prolog/epilog (pro save/restore non-clobbered registrů).
Já bych osobně doporučil nastudovat si direktivy (#pragma), napsat si makro (#define NO_UNROLL ...) a přidat to k cyklům, které nikdy nechci unrollovat. Clang tomu rozumí, GCC někdy jo a někdo ne... MSVC nevim.
BTW: Dnešní kompilery umí i "rematerializaci" - to je obejítí znovunačtení tím, že se obsah registru znovu vypočítá (třeba pokud C = A + B, ztratím C, tak si ho můžu znovu vypočítat a vyhnout se uložení a načtení do/z paměti).
tu je zaujimavy clanok o optimalizacii -O2 vs -O3 :
https://medium.com/@techhara/compiler-optimizations-can-be-tricky-ee323415e6a
31. 3. 2025, 18:37 editováno autorem komentáře
Jo a nejzajímavější je ta věta začínající „Update: …“ (na konci).
Ale k témanu Loop unrolling je pěkné toto: https://yashwantsingh.in/posts/loop-unroll/
31. 3. 2025, 21:58 editováno autorem komentáře
Prekladac by nemel menit funkcionalitu kodu, coz se u -O3 deje
Nejsem v obraze.
1) Bylo by možné být konkrétnější?
2) Chápu to tak, že po kompilaci s -O3 se to chová jinak než s -O2 . Chápu to správně?
3) Dělá to s -O3 něco, co je sice jiné ale podle normy pořád ještě přípustné, nebo to normu porušuje?
Snazi se to telepaticky hadat zamysl tvurce, coz v pripade ze je tvurce ubervelebridil. muze nektery veci zasadne zrychlit ... ale ve vsech ostatnich pripadech to vede k presne opacnemu efektu.
Je to zhruba podobny tomu, jako kdybys do databaze pridal indexy pro kazdy jedno query (coz by se dalo delat zcela automaticky) a pak se divil, proc ty inserty jsou 1000x pomalejsi (a databaze 1000x vetsi), coz by byl dusledek.