Tak ještě na závěr malé shrnutí.
1. Proč jsem sem psal něco s assemblerem. Nešlo o proovokaci, ani o výplod přeoptimalizovaného mozku:) Prostě jsem se jenom pozastavil nad ocitovaným blokem z článku, že přičítání/odčítání může být náročná operace.
A protože většinou JVM stejně běží na x86, tak jsem použil x86 instrukce, abych ukázal, že to tak být nemusí. Že je to v JVM implementováno jinak, to už je jiný topic.
2. Moderní procesory, a opět viz fakt, že JVM nejčastěji běží na x86, se snaží vykonávat kód dopředu. K tomu potřebují předpovídat skoky, jak už je vysvětleno někde v komentářích. Eliminace skoků, případně využití předvídatelných skoků proti cmov instrukcím/setbl konstrukcím ternárního operátoru pro 32bitů, je klíčem k rychlosti - viz způsob optimalizace ICC např. oproti GCC|MSVC. U bytecode se můžeme bavit jen o skocích, ale i tam to má (alespoň teoreticky) smysl.
Takže závěrečný příklad z praxe. Dostal se mi do ruky Deep-First Search napsaný v Javě se zbytečnými skoky. Když jsem ho doslova přepsal do C, tak bylo většinou rychlejší. Ale na některých datech, když cca méně než 20% uzlů vstupního grafu byly listy, tak byla Java rychlejší. Tady to má souvislost s jedním z komentářů, kde padnul názor, že je lepší psát tupý kód, který chutná překladači provádějícímu optimalizace.
A jak to pokračovalo dál? JVM evidentně dělalo runtime profiling a přepisovalo skoky podle naučené pravděpodobnosti. Jenomže tu se mohlo naučit jenom u těch cca méně než 20%. Takže Java se zbytečnými skoky začala po nějaké době dávat téměř stejné výsledky jako Java bez těch zbytečných skoků.
Když jsem vzal C program a odstranil všechny skoky, tj. podmínky, které tam nemusely být, tak Java na některých datech stále porážela C. Chápu, že něco takového vede některé lidi k úvaze, že JIT je lepší než GCC -O3.
Jenomže, když jsem využil znalost Branch Target Buffer procesoru a přidal podmínku, která tam nemusela být, tak už byl program z C vždy rychlejší než Java. Ve většině případů to urychlení bylo větší než 10x. (Ano, BFS běží ještě rychleji díky absenci rekurze.)
Plus, na řadě dat Javě došla paměť - a měla jí dost.
Takže závěr může znít: čas vynaložený na optimalizaci v Javě snadno může být ztraceným časem, protože JVM si to stejně udělá po svém. Optimalizace v C vhodnou konstrukcí cyklů a podmínek se určitě vyplatí, ale chce to znalosti, které každý nemá.
Je to "levnější vývoj v Javě" vs. "efektivnějíš kód z C".
Má smysl vědět, co JVM dělá, aby program neběžel zbytečně příliš dlouho. Ale pokud je nutné mít efektivní program, pak je prvním krokem k optimalizaci změna jazyka a tím pádem i překladače.
Vpodstatě závěr je to, co jsme tu už říkali - jde jen o to, co potřebujeme. Pro ruční optimalizace je samozřejmě výhodnější jít blíže k procesoru. A někteří hardwaráři by se na to ještě dívali s pohledem "Co to je? Vždyť by bylo mnohem efektivnější si navrhnout vlastní hardware!" Ale nejdůležitější je moct si říct "Mohl jsem to sice napsat v těchto ohledech (cena, následná údržba, rychlost, ...) lépe, ale zase by se to jinde projevilo (cena, následná údržba, rychlost, ...) a nevyplatilo by se to."
Jinak článek by mě taky zajímal. Zvlášť to zrychlení pomocí zbytečné podmínky,
V podstatě šlo o to, že se buď udělal call, nebo ret. K tomu rozhodnutí se dospělo. Takže nebylo nutné přidávat "zbytečný" extra test. Jenomže ten extra test mohl zavolat ret dřív, pokud už to bylo jasné. Takže se ušetřily jednak instrukce a jednak se instruction pointer často pohyboval v blízkosti skoků. On je tam totiž ještě limit na délku kódu, ve kterém se udržuje historie skoků - tohle mi přišlo líp popsané v manuálu od AMD než od Intelu.
Jenomže, když jsem využil znalost Branch Target Buffer procesoru a přidal podmínku, která tam nemusela být, tak už byl program z C vždy rychlejší než Java. Ve většině případů to urychlení bylo větší než 10x. (Ano, BFS běží ještě rychleji díky absenci rekurze.)
tak takhle by se teda opravdu programovat nemelo. staci, abys program spustil na necem, kde bude branch prediction udelana jinak (novejsi nebo starsi verze cpu), zkusil program prekompilovat jinde (SPARC, ARM, ...) nebo pouzil jiny prekladac a vsechny tvoje optimalizace nejenze budou k nicemu, ale muzou byt hrube kontrapudiktivni.
Optimalizace v C vhodnou konstrukcí cyklů a podmínek se určitě vyplatí, ale chce to znalosti, které každý nemá.
to je pravda... o par postu vys tu jeden expert tvrdil, ze for cyklus se preklada pomoci dvou skoku a pritom ten smejd gcc si to skompiloval posvem jenom s jednim skokem.
Jak bylo uvedeno někde výše, podmínka je zbytečná v tom smyslu, že pokud tam nebude, tak program bude fungovat taky.
Takže druhé možné přídavné jméno téhle podmínky je spekulativní. Když se vykoná, ušetří se čas vykonává následujících instrukcí, ze kterých se její efektivita zaplatí i na procesoru bez branch prediction.