Portál AbcLinuxu, 30. dubna 2025 07:56
Máme tady další speciální případ, kde se nahromadilo něco přes 70000 hozených výjimek. Speciální hlavně proto, že na stejném testcase aplikace přeložena a spuštěna na CentOS 4 běží několikrát pomaleji než když je přeložena a spuštěna na CentOS 5 (stejný stroj).
Nejpodstatnější jsou tyhle funkce:
Zbytek funkcí v grafech je z libgcc_s.so. Všimněte si rozdíl ceny _Unwind_Find_FDE, jednou 81.69G, pak 1.47G.
Zvláštní je, že když se verze přeložena na CentOS 4 spustí na CentOS 5, je pořád stejně pomalá. I když používá stejné knihovny libgcc_s.so, libstdc++.so, libdl.so a libc.so (ověřil jsem pro jistotu přes /proc/PID/maps), kde profiler uvádí nejvíc stráveného času.
Situaci navíc komplikuje fakt, že funkce, která sama spolkne 45% času, nemá viditelné jméno (nejspíš kvůli hidden-visibility), asi to bude něco jako _Unwind_IteratePhdrCallback.
Zatím jsem identifikoval následovnou kombinaci okolností, proč rozdíl ve výkonu nastává:
Nerozumím, proč _Unwind_Find_FDE musí prohledávat sdílené knihovny. (Hledání destruktorů pro úklid nebo typové informace o "hozeném" objektu? Zbytek informací o "exception contextu" by měl být přímo na zásobníku.). Pročítal jsem zdrojáky gcc a glibc kolem zmíněných funkcí, googlil, ale pořád mám jenom dost mlhavou představu jak gcc implementuje výjimky ("document me!" u Language-independent routines for exception handling je povzbudivé).
Tak jsem udělal ještě jeden pokus. Klíčem byl update binutils. Na CentOS 4 jsem nainstaloval binutils-2.18 (skompilované z vanilla zdrojáků). Původně jsem chtěl ověřit, jestli aplikace přeložena na CentOS 4 bude stejně pomalá jako ta předtím, ale ta samá binárka rychlá na CentOS 5 když použiju linker flag hash-style=both (glibc na CentOS 4 použije sysv-style .hash sekci ELF souborů, protože .gnu.hash nezná; glibc na CentOS 5 by pak použila gnu-style .gnu.hash sekci ELF souborů). Tak jsem celou aplikaci vycleanoval, opět přeložil a slinkoval.
Výsledek je překvapivě ještě lepší - s použitím binutils-2.18 je to stejně rychlé jak na CentOS 4, tak i CentOS 5. Novější binutils zřejmě generují .hash sekci lépe, tím pádem je unwind po výjimce rychlejší.
Pozn.: pokud se aplikace překládá s novými binutils na CentOS 4 (jehož glibc nepodporuje gnu-style .hash), je potřeba použít linker flag hash-style=sysv, jinak to nepůjde spustit na novějších systémech podporujících gnu hash-style (na CentOS 4 to vygeneruje špatně .gnu.hash sekci).
Tiskni
Sdílej:
Kiež by sa všetky dementné veci dali vypnúť
Kdyz uz nekdo vyjimku v programu generuje, tak ji nelze vypnout, ale zachytit.Nebo cekat, ze ji nekdo "zvenku" zachyti za nas.
Ledaže by sa kompilátoru dalo pomocou nejakého flagu povedať, aby všetok kód pre výnimky ignoroval
IMHO vubec nejde o jazyk, specialni situace nastanou, at je to C, C++, Java, Python nebo Erlang. Staci par milionu radek kodu, nekolik 3rd-party knihoven a ono to uz pak jde samo.
Specialne kdyz to pak dostanou do ruky uzivatele, to teprve koukame pri nejakem demu, jake psi kusy s tim delaji. Nevychazime z uzasu, ze to funguje a nepada
Pokaždé! se kontrolují vstupní parametrySlyšel jsi někdy o bezpečnosti, robustnosti, spolehlivosti a strukturovaném programování? Kontrola chybových stavů je jedním ze základních stavebních kamenů kvalitního softwaru, a jestli jí přisuzuješ řádový výkonnostní propad, pak ti doporučuju vrátit maturitu a nechat si na základní škole detailně vysvětlit význam slova výjimka. Mimochodem, kdysi jsem kdesi zaslechl, že třeba kontrola mezí polí je na x86 jedna instrukce procesoru. To jistě vyrábí takový overhead, o kterém mluvíš?
if (!zapiš) { vyhoď výjimku }než
if (size <= 0) { vyplivniVýjimku } if (data == null) { vyplivniVýjimku } if (data.length < size) { vyplivniVýjimku } if (!zapiš) { vyhoď výjimku }
Systemove volani (open, write, ...) je drahe. Oproti tomu testovani na data.length je nepatrne...
Pořád nechápu, proč to kontroluje java ... vždyť to kontroluje systém ještě jednou po ní. Ať se snaží o výjimku, až systém vyplivne nepovedlo se a nastaví errno. Rozhodně je míň instrukc
Až na to, že systémové volání potřebuje jenom na přechod do kernel módu přes tisíc strojových instrukcí. A na návrat zpět rovněž.
Takže otestovat to a vyhnout se případnému systémovému volání ušetří tisícovky instrukcí.
Vy si vůbec neuvědomujete, jak moc drahé je volná API systému. Proto také Java, ani C/C++ nevolají funkcí zapiš systémové volání. Většina volání funkcí zapiš ve skutečnosti jen uloží data do bufferu a systémové volání provede až v případě, že je potřeba zapsat dostatečně velký blok. Tudíž stejně to musí Java, nebo C/C++ zkontrolovat.
Zkuste si sám volat přímo API systému pro krátké zápisy a budete překvapen jak moc program zpomalíte, zvláště když budete zapisovat třeba po jednom bajtu.
Druhá věc je, že testovat vstupní parametry je povinnost a nevěřit ničemu rovněž.
Každé systémové volání je drahé a sežere spoustu času. Systémová část běhu procesu je také drahá, kernel ho musí ošetřovat za úplně jiných podmínek a s úplně jinými parametry, než uživatelskou část běhu procesu. A samotné přepnutí mezi uživatelskou a systémovou částí běhu tam i zpět také není zadarmo.
Vezměte si obyčejnou libc a uvidíte, že se tam většina funkcí vyhýbá volání systému co to jenom jde, jako kdyby to bylo něco prašivého.
Třeba fread a fwrite bufferuje a snaží se minimalizovat počet volání systému pro čtení a zápis. Ve skutečnosti jen menšina volání funkce fread, nebo fwrite způsobí také volání operačního systému, naprostá většina se většinou vyřídí jen uvnitř v bufferech.
Nebo malloc / free také jede jako buffer. Nevolá funkce operačního systému na přidělení paměti, pokud je to jenom trochu možné. Namísto toho si od systému nechá přidělit větší blok paměti (tedy jedno volání systému) a ten pak drobí a kouskuje jen aby nemusel systém volat.
Systémové volání je prostě drahé, drahé, drahé.
Ještě je třeba dodat, že většina operačních systému penalizuje procesy, které stráví příliš mnoho času v systémové části běhu. A do toho se počítají samozřejmě i hojná volání systémových funkcí jenom proto, co si proces mohl otestovat sám daleko levněji.
Pokud strávíte mnoho času v systémovém běhu, pak hodně operačních systémů procesu myšleně snižuje prioritu a plánovač ho odsouvá do pozadí.
A co byste rekl na takovyhle priklad: mam vlakno ktere je dedikovane na to aby cekalo v poll-u, kdyz jsou k dispozici nejaka data na nejakem fd tak poslete zpravu. Cele je to hezky zapouzdrene v knihovne glib. Shodou okolnosti se stane ze jedno z TCP spojeni protistrana uzavre. Nejake jine vlakno to odchyti a osetri. Vlakno ktere ceka v pollu se rozebehne - poll skoncil s chybou(POLLNVAL invalid fd). A co ted budete delat? Samozrejme ze mate errno - jste uvnitr knihovny(glib) a nevite nic o nadrazene aplikaci(evolution). Ted mate vyber:
1. Vratit chybu, ale API vam to neumoznuje
2. Vyhodit vyjimku a osetrit ji ve vyssi vrtstve
3. Precist 0 bajtu z fd stderr a zavolat poll jeste jednou(ten samorzejme okamzite skonci s chybou).
Ivan
PS: c je spravne.
Já myslím, že
a) možností je daleko více, než 3
b) správná odpověď závisí na správném návrhu aplikace
c) já jsem neřekl, že systémové volání je fuj, jen, že by se nemělo volat zbytečně Volat systém je samozřejmě potřeba. Není třeba všechno házet do extrému. V programování můžete dělat cokoli, když víte co děláte, jaké důsledky bude mít to co děláte, a řešení splňuje vše, co je potřeba.
V zásadě v programování existuje spousta škodlivých dogmat, které je dobré, aby se jich drželi začátečníci (neboť potřebují získat dobré návyky), ale později je třeba je opustit. Například zbytečná dogmata:
„goto je špatné“
„budu ďábelsky šetřit jednu pikosekundu (analogicky instrukci) a myslím si, že to má smysl“
„myslím ve strojáku a lžu sám sobě, že umím programovat ve vyšším programovacím jazyce (častý jev u Céčkarů)“
„teorie je zbytečná“
„jsem dobrý programátor, protože umím nazpaměť všechna volání knihoven i bez dokumentace (častá iluze u programátorů bez talentu)“
„ošetřování chyb návratovou hodnotou, nebo přes errno je efektivnější, než přes výjimky“
„když něco o programování říká známá osoba XY, která toho hodně naprogramovala, tak to musí být pravda (asi největší z omylů)“
a řada dalších
Přičemž některé z výše uvedených jsou za jistých okolností i pravdivé, ale za jiných okolností zcela mylné.
Pořád nechápu, proč to kontroluje java ... vždyť to kontroluje systém ještě jednou po ní. Ať se snaží o výjimku, až systém vyplivne nepovedlo se a nastaví errno. Rozhodně je míň instrukcí
Java běží na cca 10 různých operačních systémech. Každý z nich se chová trochu odlišně. Java musí garantovat, že z pohledu aplikací bude chování virtuálního stroje všude stejné a že budou vynucována stejná omezení.
Zase jeden co si myslí, že kompilátor musí do strojáku přeložit doslova vše co je ve zdrojáku.
Zase jeden co si myslí, že počet instrukcí nějak určuje, jak rychlé to bude.
Oba předpoklady jsou totální bludy.
Ještě Vám prozradím jedno sladké tajemství. Test na null se dá udělat zcela bez overheadu a bez jakékoli instrukce právě pomocí výjimek. Takhle testuje třeba kernel Windows, aby to bylo rychlejší. Prostě na null adrese není platná stránka a kdykoli procesor najde instrukci, která míří na null adresu, pak není třeba nic testovat. Prostě na null adresu přistoupí, procesor to zjistí a vyhodí výjimku procesoru (hw přerušení). Aplikace, nebo kernel jen výjimku chytne a rozluští odkud to bylo. Máte test na null a nulovým overheadem bez jakékoli instrukce.
Test na hranici polí, nebo jiná čísla v mezi se dá udělat jedinou instrukcí bound. Žádné if nenásleduje, protože v případě mezí mimo rozsah procesor vyhodí výjimku (přerušení číslo 4) a opět viz předchozí odstavec.
Právě výjimky umožňují neuvěřitelně zefektivnit obsluhu chybových stavů. Dobře udělaný kód s výjimkami bude běžet výrazně rychleji, než kód, kde se testují chyby pomocí errno. Jenže to nepochopí nikdo, kdo s chybně myslí, že kompilátor je blbec, který otrocky překládá do strojáku. Kompilátor má dnes výkonnou optimalizační jednotku, která tvoří 99% kódu kompilátoru – a u některých kompilátorů je tak dobrá, že předstihne 999 assembleristů z tisíce. Pro jistotu: nemluvím o gcc.
a u některých kompilátorů je tak dobrá, že předstihne 999 assembleristů z tisíce.
To rád slyším
nemluvím o gcc.
O čem tedy?
O Intelu, který je špičkou. Trochu o Microsoftu, který je sice o hodně za Intelem, ale taky se to dá.
gcc byl špičkou před deseti a více lety. Ale mám pocit, že právě obrovská multiplatformovost působí, že není tolik času na to dotáhnout optimalizace do excelentního stavu.
Kdysi tomu také bránil použitý vnitřní model pro popis optimalizace, tuším že na řadu případů v dnešních strojáku trochu drhnul. Ale jak je to dnes netuším, protože detaily jsem přestal sledovat.
Neříkám, že gcc špatný kompilátor, jen na abclinuxu.cz to zdůrazňuji, protože je tu přehršel blogpostů, komentářů atd., na kterém je jasně vidět, že si lidé plotou C/C++ s gcc a mají pocit, že je to jedno a totéž. A když něco gcc udělá špatně, tak píšou „C/C++ je špatné“ namísto toho aby obvili open source projekt gcc. Takže někdy to zdůrazňuji přespříliš.
O Intelu, který je špičkou. Trochu o Microsoftu, který je sice o hodně za Intelem, ale taky se to dá.ma te nejake benchmarky, ktere to ukazuji? nebo jsou to zase nepotvrzene domenky? moje zkusenost je prave opacna, tj. v pripade cisteho C GCC netrhne ICC prdel bez vetsi namahy. (a nezavisle mi to potvrdili i dalsi lidi)
mate nejake benchmarky
nemám, respektive je nechci hledat
v pripade cisteho C GCC netrhne ICC prdel bez vetsi namahy
vimu natrhne prdel (z hlediska výkonu, tedy efektivity práce) bez větší námahy i nejblbější plain text editor (třeba notepad ve windows), pokud u něj sedí neschopný začátečník
pokud u vimu sedí profík, je to právě naopak
a stejné je to s icc. icc je profi nástroj, který velmi tvrdě reaguje na options a nastavení kompilátoru. daleko citlivěji a s daleko většími efekty a důsledky na optimalitu programu, než je tomu u gcc
defaultně se nepřekládá na max. optimalizaci, to se dělá až u release verze. defaultně se překládá na slušnou rychlost překladu
a stejné je to s icc. icc je profi nástroj, který velmi tvrdě reaguje na options a nastavení kompilátoru. daleko citlivěji a s daleko většími efekty a důsledky na optimalitu programu, než je tomu u gccmuzete to opet nejak dolozit?
Můžete si to bez problémů ověřit. Stejně jako naprosto každý člověk.
Cece, pane Ponkraci, musim vam podekovat za ten uvod do prekladacu, assembleru a optimalizaci, doted jsem v tom tapal.
Nevim, proc mluvite o testu na null, zde se vede debata o kontrole rozsahu poli. A kdyz se trochu zamyslite, tak zjistite, ze v tomhle pripade je vam zachytavani pristupu na nepridelenou pamet k hovnu.
Kdyz uz tady chcete machrovat s bound
em, tak byste mel vedet, ze vyvola preruseni cislo pet, nikoliv ctyri, a hlavne na starsich pentiich (nevim jak je to ted) to spotrebovalo vic cyklu, nez kdyz jste ta porovnani rozepsal na jednotlive instrukce, takze se v realu moc neuchytil.
Puvodni vyrok kolegy Ladicka byl samozrejme dementni v mnoha smerech, presto jsem se rozhodl sahodlouze nevysvetlovat co vsechno je v jeho predpokladu spatne a misto toho rozcechral jeho nevedomost o neco mene dementnim rozvinutim jeho uvahy.
A já se nemůžu dívat jak se tu hádáte a jdu radši psát svůj program v Javě
Čéče, skvělý!!!
Konečně jsem se zasmál. Osvěžující styl!
Ohledně testu na null, zkuste trochu prohlédnout vlákno diskusí, než zareagujete. Viz úryvek zdrojáku, na který jsem reagoval:
if (size <= 0)
{
vyplivniVýjimku
}
if (data == null)
{
vyplivniVýjimku
}
Ohledně bound: Procesory se vyvíjejí a závislosti rychlostí rovněž. Například hodně staré procesory nebyly příliš náchylné na zpomalování při skocích. Dnešní procesory skoky dost penalizují, zejména pokud to neodhadnou. A možná byste mohl všimnout, že bound neobsahuje instrukci skoku, což zase hodně zvýhodňuje.
Procesorové cykly už jsou také nezajímavé, protože při paralelním zpracování, které je dnes v procesorech je pouhé číslo procesorových cyklů nezajímavých a fakticky téměř nic nevypovídajících. Paralelní zpracování je v procesor už trochu déle, mohl byste si toho všimnout.
Hlavním problémem bound, nicméně dříve fakticky nevadícím je fakt, že v 64 bitovém módu nejde použít.
Na to jste sice reagoval, ale ve vedlejsim vlakne, ktere sice vychazi ze stejneho rodice, ale resi uplne jiny problem. Takze bych poprosil, abyste nam v tom hezkem stromovem grafu nedelal bordel zavadenim cyklu.
Bound neobsahuje instrukci skoku? Muj ty Tondo kolenatej... A jak se asi procesor dostane na tu obsluhu preruseni? Teleportuje se tam pres dimenzi X?
No jestli jsou nezajimave cylkly, tak stejne pase jsou i instrukce a nechapu proc se tu prsite, ze bound je jedna instrukce. Mohl byste si vsimnout, ze procesor umi paralelne zpracovavat nejen nekolik mikro-ops z jedne komplexni instrukce, ale dokaze najednou vykonat mikro-ops z nekolika ruznych instrukci. Takze se vam muze stat, ze v jednom taktu se provedou dve pricteni a tri presuny v registrech, tj dohromady pet instrukci. Pocet cyklu nam rika, ze jeden mov
neni pro procesor stejne narocny jako bound
nebo fsincos
.
No vidite, to jsem ani nevedel. Asi tu pomalost bound
neopravili ani pozdeji a nikdo to nepouzival, az ji nakonec AMD z 64 bitu vyrazilo uplne.
takze budeme radeji verit programatorum, ze vedi, co delaji? dekuji nechci, takovych je tak promile
To je cena za multiplatformovost. Pokud napíšete kompilátor na x procesorových architektur, pak to zkrátka nebude tak efektivní.
1) Cena za výjimku je věc, která velmi závisí na kompilátoru. Rozdíly mohou být velmi drastické – používejte dobrý kompilátor.
2) Cena za výjimku je věc, která může záviset i na operačním systému. Například Windows mají podporu výjimek přímo v jádře a modrá obrazovka smrti není nic jiného, než informace o neobsloužené výjimce v kernelu. Pro znalého člověka je v ní obrovské množství informací.
3) Cena za výjimku je věc, kterou rapidně ovlivňuje programátor v obrovských mezích. Je rozdíl zda výjimku vyhodíte pointerem, referencí, objektem, i čím jí chytíte. Dále je obrovský rozdíl podle toho co typ výjimky obsahuje. I zde se dají měnit hodnoty o stovky procent v rychlosti.
4) Nešikovně udělané výjimky ve sdílených knihovnách musí postupovat podle nejpesimističtější varianty a tudíž jsou pomalé. Proto já v C++ projektu vše, co má házet výjimky zkompiluji svou verzí kompilátoru a stejnými optiony.
1) Multiplaformnost je soucast specifikace dane aplikace (musi to bezet na ruznych distribucich Linuxu, taky na Win XP a Vista). Jenom pro porovnani, stejny testcase na Win XP prelozen s msvc 7.1 bezi stejne rychle jako ten na CentOS 5.
2) Tady je videt, ze rozdil dela glibc, nikoli kompilator (ten je stejnej).
3) Tyhle vyjimky se chytaji referenci, aby se predeslo zbytecnemu kopirovani. Vyjimka obsahuje kratky std::string (cca 20 znaku) a jeden int. V mereni profileru konstrukce a destrukce tech vyjimek padne pod 0.03 procenta.
4) Ta prvni veta mi vubec nedava smysl, muzete to nejak objasnit? "svou verzi kompilatoru" = vlastni kompilator/patchnuty kompilator?
Ad 1) a Ad 4) Cena za výjimku je tvrdě závislá na nastavení kompilátoru. Kompilátor se rozhoduje, jak moc pesimisticky bude ošetřovat výjimky. Je to podobné jako v databázích, v db existují 4 isolation levels podle toho jak moc chcete oddělit transakci od okolního světa. Pokud pojedete v db na nejvyšší stupeň, pak sice máte dokonalé transakce ale naprosto mizerný db výkon. A podobné je to s výjimkami.
Kompilátor se rozhoduje, jak moc důkladně musí výjimku ošetřovat. V nejpesimičtějším případě předpokládá, že každá strojová instrukce může vyhodit výjimku (tedy cokoli v kódu, kdekoli a nečekaně), dále tvrdě předpokládá mnoho seznamů pro ukončovací akce (důsledek RAII konceptu, rušení lokálních objektů), a tvrdě projíždí mnoho seznamů.
Každý (dobře optimalizující) kompilátor se snaží zbavit tvrdých předpokladů na nutnost ošetření výjimky a detekovat místa, kde to nemusí dělat tak tvrdě a bez následků. Jakmile mu do kódu zavedete nečekaný kód třetí strany (knihovnu, kterou nepřeložil sám ve stejném režimu a nemá od ní informace), pak musí předpokládat nejpesimičtější variantu, čímž výjimky saktra zpomalí.
Výše uvedené údaje jsem velmi zjednodušil až na hranici polopravdy, ale nechce se mi to celé vypisovat. Podstata je jasná.
Kompilátor se rozhoduje, jak moc důkladně musí výjimku ošetřovat. V nejpesimičtějším případě předpokládá, že každá strojová instrukce může vyhodit výjimku (tedy cokoli v kódu, kdekoli a nečekaně) [...]Každý (dobře optimalizující) kompilátor se snaží zbavit tvrdých předpokladů na nutnost ošetření výjimky a detekovat místa, kde to nemusí dělat tak tvrdě a bez následků. Jakmile mu do kódu zavedete nečekaný kód třetí strany (knihovnu, kterou nepřeložil sám ve stejném režimu a nemá od ní informace), pak musí předpokládat nejpesimičtější variantu, čímž výjimky saktra zpomalí.
Nepletete tady dohromady vyjimku procesoru s vyjimkou jazyka C++? Prekladac vi, kde ma na zasobniku udelat exception context (klauzule catch, nebo throw() specifikator).
Navic u C++ kompilator az na nejtrivialnejsi pripady nemuze vedet co vyhodi vyjimku (proto se ani specifikator throw() nejak moc nepouziva). Pri vyjimkach pres hranice knihoven je dulezite, aby kod v knihovne, ktera odchytava, mel dostatecne typove informace o vyjimce, co je hozena (musi byt viditelna pres hranice dll atd). V tomhle ale i tak problem nebyl (hazelo se pres "nase" knihovny skompilovane stejnym kompilatorem se stejnymi parametry). Viz komentar o binutils nize.
Moc nerozumím, jak moc to souvisí s tím co jsem napsal.
C++ výjimky jsou na úrovni C++ primitivní, ale implementačně musí kompilátor hodně pracovat. Pokud by udělal kompilátor opravdu vše na 100%, pak by byly výjimky hodně pomalé a datové struktury kolem výjimek by zabíraly podstatnou část programu. A tak to také bylo u prvních kompilátorů, a z nadávek na první implementace v této prehistorické době dodnes čerpají argumenty zarytí odpůrci výjimek.
Například Symbian se díky prvním neefektivním implementacím rozhodl výjimky nepoužít a nahradit je svým mechanismem zvaným „leaving“, což myslím spolu s dalšími náhradami mu láme vaz. Programování pro Symbian je nechutně složité a náhrady nemají (dnes) žádné opodstatnění.
Proto trvalo mnoho let a mnoho pokusů a nápadů, než se podařilo implementovat výjimky v C++ efektivně. Byla to dlouhá cesta. Na úrovni C++ syntaxe se vůbec nic nezměnilo, implementačně ve strojáku výsledného kódu je to zcela jinak.
Základním pravidlem C/C++ kompilátoru je: Nemusím vykonat co je ve zdrojáku, ale musím zajistit, aby výsledek celé akce byl stejný, jako je ve zdrojáku. U výjimek kompilátor hodně šidí – tedy optimalizuje, co vše lze vypustit. A není toho málo. Pokud by měl dělat vše na 100%, je to sice výrazně efektivnější, než v dřevních dobách, ale přeci jenom dost pomaleji, než když může optimalizovat.
Ostatně zajímavá je i ta typová informace. I ta musí mít nějakou implementaci. Pomíjím možnosti, že třída moje_vyjimka může mít v modulu A jinou definici a podobu, než v modulu B, to se totiž také může stát. A i typová implementace může být efektivní i neefektivní.
Ono se za výjimkami ve výsledném kódu děje hodně, mechanismus výjimek je hodně vysoká abstrakce z hlediska C++ zdrojáku. Navíc to musí být rychlé. A v pozadí se toho děje hodně moc, více, než se jeví z pohledu syntaxe C++.
2) Cena za výjimku je věc, která může záviset i na operačním systému. Například Windows mají podporu výjimek přímo v jádře a modrá obrazovka smrti není nic jiného, než informace o neobsloužené výjimce v kernelu. Pro znalého člověka je v ní obrovské množství informací.Moc tomu nerozumím, ale není SEH ve Windows něco trošku jiného? Nebo ses tím dí skutečně pracovat jako s normální C++ výjimkou?
V MSVC jsou všechny výjimky včetně C++ postaveny nad SEHem. SEH umožňuje propašovat libovolná data jako součást výjimky a MSVC toho patřičně využívá. Proto nezachycená výjimka v C++ programu je zachycena systémem Windows.
Implementačně v MSVC je C++ vnitřně postavena jako SEH výjimka.
SEH je univerzálnější, než C++ výjimka, proto se SEHem nedá pracovat jako s C++ výjimkou, ale C++ výjimka je postavena jako jedna z možností SEHu.
Ale třeba MSVC umožňuje automaticky SEH výjimku konvertovat na libovolnou C++ výjimku vlastní třídy, kterou si vyrobím.
Ovšem jak to dělá gcc na Windows netuším a vzhledem k nižší kvalitě kódu lezoucí na Windows z gcc jsem to moc nerozebíral.
Tak jsem udělal ještě jeden pokus. Klíčem byl update binutils.
Na CentOS 4 jsem nainstaloval binutils-2.18 (skompilované z vanilla zdrojáků). Původně jsem chtěl ověřit, jestli aplikace přeložena na CentOS 4 bude stejně pomalá jako ta předtím, ale ta samá binárka rychlá na CentOS 5 když použiju linker flag hash-style=both (glibc na CentOS 4 použije sysv-style .hash sekci ELF souborů, protože .gnu.hash nezná; glibc na CentOS 5 by pak použila gnu-style .gnu.hash sekci ELF souborů). Tak jsem celou aplikaci vycleanoval, opět přeložil a slinkoval.
Výsledek je překvapivě ještě lepší - s použitím binutils-2.18 je to stejně rychlé jak na CentOS 4, tak i CentOS 5. Nevím co ty starý binutils dělaly...
Donutily kompilátor uvažovat pesimisticky při správě výjimek.
Kompilator byl v obou pripadech (CentOS 4, CentOS 5) stejny - gcc 4.1.2. Co se zmenilo, byl pouze linker (z novych binutils), ktery nejspis postavil hash tabulku symbolu (sekci .hash v ELFech) jinak, tim padem je vyhledavani rychlejsi. Prokazuje to i nove mereni profilerem, kde je videt ze zpusob volani _Unwind_RaiseException, _Unwind_Find_FDE a dl_iterate_phdr se nezmenilo, jenom callback funkce volana pres dl_iterate_phdr se "zlevnila" (a ta callback funkce je ze stejne libgcc_s.so jako v problematickem pripade).
Update:
pokud se ta aplikace preklada a linkuje na CentOS 4 (systemu, ktereho glibc nepodporuje gnu hash-style) s temi novymi binutils, je potreba pouzit linker flag hash-style=sysv. Jinak to nejak spatne vygeneruje tu .gnu.hash sekci v ELFu a pak binarka na systemech podporujicich .gnu.hash hazi "undefined symbol". S tim hash-style=sysv je to OK, i zrychleni proti starym binutils zustava.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.