Portál AbcLinuxu, 30. dubna 2025 19:50
Programoval jsem a programuji řadu let v C/C++ a programuji i v Javě. Nemám v podstatě žádný důvod preferovat jeden či druhý jazyk, protože každý z nich má své silné a slabé stránky. Líbí se mi oba jazyky, každý z nich má něco do sebe.
Pohybuji se tedy v obou komunitách a za posledních několik let jsem si všiml zajímavé zákonitosti. Jakoby pro zastánce Javy existovala dvě dogmata, a kdykoli se pokusím na tato témata polemizovat, začínám mít pocit, jako kdybych zastáncům Javy ubližoval a ničil všechno, co Javistům drahé a cítím se jako bídák. A pokud bych snad vyslovil pochybnosti o těchto dvou dogmatech, tak cítím, že buď mě dotyčný Javista začne nenávidět, nebo se dá na cestu alkoholismu. Každopádně racionální debata v těchto směrech není možná.
První dogma zastánců Javy je o tom, že Java je nejvíce (případně nejlépe) objektově orientovaný jazyk, který existuje. To je sice trochu diskutabilní, ale na to jsem celkem zvyklý, s naprosto se stejným dogmatem jsem se dříve setkával u zastánců Smalltalku. Předem říkám, že na téma prvního dogmatu Javistů nehodlám diskutovat, myslím se své a nepolemizuji. Druhé dogma je zajímavější a to je ohledně rychlosti Javy.
Druhé dogma se projevuje zajímavěji. Čas od času se objeví test, který srovnává rychlost C/C++ a Javy. Ve všech těcto testech dopadá Java výborně a neřídka se ukáže, že Java je rychlejší, než C/C++. Celé by to bylo v pořádku, kdyby se v daných testech vždycky neobjevila nějaká ta nenápadná trhlina, která naznačuje, že to není až tak nestranně provedený test.
Proč to celé píšu? Protože by mě vážně zajímalo, jak to s tou rychlostí Javy skutečně je. Prošel jsem už mnoho testů, ale žádný z nich nebyl seriózně provedený. Většina testů ve skutečnosti trpěla hrubými nedostatky, které vyplývaly zejména z toho, že testovaný program byl dobře napsán v Javě, ale značně neoptimálně a špatně v C/C++. Prakticky všechny testy srovnávající rychlost Javy s C/C++ trpěly následujícími body:
Proč o tom píšu? Zrovna včera jsem byl upozorněn na takový test uveřejněný zde na abclinuxu.cz. Dokazuje, že Java umí být rychlejší, než C. Najdete jej tady. Bohužel je to naprosto typický příklad toho, jak nedělat seriózní test. Snaha možná byla, ale provedení vázne. Co je na něm špatně? Hlavní výtky bych shrnul do bodu 1), 2) a 4) v mém přehledu viz výše. Bohužel takto dopadají všechny srovnávací testy od Javistů. Stačil malý pohled do testovacího programu psaného v C, abych pochopil, že tento program je psán typicky Javovským způsobem a velmi neoptimálně a neefektivně. Není potom ani divu, že program v C byl pomalý, ale to není chyba Céčka, ale autora programu. Mohl bych napsat mnoho a mnoha stran, kde bych popsal, co všechno je špatně v Céčkové verzi testovacího programu, ale spokojím se jen s jednou ukázkou, aby tento článek neměl příliš velký rozsah. Například tento úsek:
double MonteCarlo_integrate(int Num_samples) { Random R = new_Random_seed(SEED); // Funkce new_Random_seed alokuje strukturu pomocí malloc // a naplní jí daty. // Pokračuje nějaký kód funkce. Random_delete(R); // Funkce Random_delete nedělá nic jiného, než uvolňuje // strukturu pomocí free. }
To je typická ukázka Javoviny, kterou by Céčkař nepoužil, protože je to značně neoptimální. Prostě vytvářet strukturu, kterou použiji ve funkci tak, že jí alokuji dynamicky na hromadě a pak uvolním na hromadě je neefektivní a pomalé. V Javě to ovšem jinak nejde, ale v Céčku je daleko přirozenější (a výsledný kód bude daleko rychlejší) napsat to tímto způsobem (tedy nechat strukturu vytvořit na zásobníku):
double MonteCarlo_integrate(int Num_samples) { Random_struct R; // Vytvoření struktury na zásobníku. initialize_Random(&R); // Naplnění struktury daty. // Pokračuje nějaký kód funkce. // Uvolnění struktury není potřeba, protože se ze zásobníku // uvolní automaticky po skončení funkce. }
V Céčkové verzi testovacího programu je přehmatů daleko daleko více a zároveň by se slušelo zamyslet nad některými datovými strukturami v programu, které by šly taky hodně zeefektivnit. Dost triskní je i způsob překladu Céčkového programu, autor testu třeba nevěnoval vůbec pozornost tomu, aby alespoň nastavil překlad pro správný procesor. Naproti tomu u Javovské verze udělal úpravy, které vedou ke zvýšení rychlosti programu. Takto si opravdu seriózní test nepředstavuji, kdy Javovský program je napsán co nejlépe a Céčkový program je napsán prasáckým způsobem, špatně a neoptimálně.
Existuje vůbec nějaký férový test rychlosti Javy, případně srovnání rychlost Javy a C++? Za férový test považuji jedině to, že proti sobě budou testovány dva programy, jeden v Javě, druhý v C++, které budou dělat naprosto stejnou činnost. A každý z obou programů bude maximálně optimalizován na rychlost. To znamená, že C++ program nebude psát přeučený Javista, který se s tímto jazykem před chvílí seznámil, ale člověk, který umí. A to samé platí o Java programu. Ale prosím seriózně, špatně provedených testů už mám plné zuby.
Tiskni
Sdílej:
new Random().nextInt() // popř. někam objekt uložitpodstatně náročnější než
rand()Tohle asi není dobrá ukázka toho, co chci říct...ale jde mi o to, že kód v C/C++ bude vždy rochu víc low-level a bude tak přecejen snáze optimálně zkompilován.
Že by chybějící '-fomit-frame-pointer
'?
Dále se domnívám, že u alokací většího množství paměťových bloků by Java nad C/C++ také vyhrála.
Chcete říct, že by v tomto specifickém případě vyhrál defaultní alokátor paměti Javy nad implementací malloc()
v glibc resp. standardním new
? To je dost dobře možné, ono totiž nelze napsat alokátor, který by byl nejefektivnější ve všech situacích, protože ty požadavky jsou do značné míry protichůdné. Právě proto máte možnost použít vlastní implementaci malloc()
resp. new
. Ale to vlastně jen na konkrétním příkladu demonstrujeme to, o čem psal autor příspěvku…
Tohle tvrzení už jsem párkrát viděl. Ale nikdy jsem nikoho neviděl napsat, co tím výrazem matematické operace vlastně konkrétně myslí. Můžete uvést nějaký příklad pro představu?
GUI aplikace jsou pomalejsi nez nativni, protoze swing je takhle navrzen (ale verze 1.5 prinesla zlepseni).Troufám si tvrdit, že je dnes (1.5) Swing rychlejší než SWT. Nemám pro to žádné důkazy, ale srovnatelné programy používající to či ono (např. Eclipse vs. Netbeans) jsou na stejném stroji různě rychlé (Swing rychlejší). Paměťová náročnost obojího je ovšem ukrutná (u větších aplikací)...
Chtěl bych vědět, jak moc je skutečně pomalá, či rychlá.Teď jsem si vzpomněl, že jsem kdysi někde potkal nějaké rozsáhlejší testy. Neporovnávaly (podle mnoha kritérií) jen Javu s C a C++, ale také C/C++ kompilované různými kompilátory, a ještě nějaké další jazyky. Už si ale nevzpomenu, kde to bylo.
Java je objektove orientovany jazyk. Je mnohem vice objektove orientovany a lepe navrzeny nez C++, coz je pokus naroubovat OO principy na C.Podle ceho soudite, ze je lepe navrzeny?
Jednoduchý příklad: Wirth se rozhodl, že pro porovnávání použije '=
' a pro přiřazení ':=
', protože to odráží logiku obvyklého matematického zápisu. Kernighan a Ritchie si udělali malou statistiku a zjistili, že přiřazení se v programech používá podstatně častěji než porovnávání na rovnost, takže zvolili '=
' pro přiřazení a '==
' pro porovnávání. A podobný rozdíl v logice uvažování se potom projevuje i v podstatnějších otázkách návrhu jazyka.
To je právě hlavní síla C a C++: jsou to jazyky, které nebyly navrženy podle určité teoretické vize, ale podle potřeb těch, kdo je budou používat. Proto jsou sice C a C++ z teoretického hlediska navrženy nesystematicky a mnohdy nelogicky, ale pro praxi se hodí daleko více.Toto právě platí i o Javě. Vlastně možná především o Javě... Pouze to zaměření je výrazně jiné, než u C++
AutoLISP má s dnešním LISPem asi tolik společného jako K&R C s ISO C 99...Já žiju v představě, že LISP je čtyřicet let stále stejný, a to zejména z toho důvodu, že není možné ho jakkoli vylepšit :)![]()
A taky proto, že dvoustupňová kompilace Java -> byte kód a pak JIT kompilace byte kód -> strojový kód musí být alespoň o pár procent méně efektivní, než přímá kompilace C++ -> strojový kód.Proč? Ale vážně. Je to spíše naopak - JIT může použít optimalizace na základě aktuálních informací. Tedy například konkrétní typ procesoru, ale především na základě reálných informací o běhu. Typicky může nejčastěji volané metody vložit (inline), smyčky částečně (nebo úplně) rozvinout a podobně. Samozřejmě pokud mluvíme v teoretické rovině. V praxi mají ty nejlepší kompilátory C a Fortranu stále výrazný náskok a nejspíše ho udrží.
Mě by taky zajímalo, jak by se Java rychlostně vypořádala třeba s tím, kdyby C++ začalo používat vektorové instrukce v procesoru. Jestli vůbec Java je schopná třeba optimalizovat stejným způsobem.Viz výše - proč by ne? Dokonce to AFAIK některé JIT používají, ale nevím v jakém je to stavu - a hledat to nebudu, raději půjdu spát.
Jeden důvod bych viděl: úspěšnost generování optimálního cílového kódu závisí mj. na tom, jaké máš o programu informace. V nějakém mezikódu (bajtkód či cokoliv jiného) jich určitě máš míň, než třeba v abstraktním syntaktickém stromu (proto třeba překladač Flexu negeneruje v průběhu činnosti žádný mezikód, pokud si to dobře pamatuju :) ). To je jediné, co mne napadá.A taky proto, že dvoustupňová kompilace Java -> byte kód a pak JIT kompilace byte kód -> strojový kód musí být alespoň o pár procent méně efektivní, než přímá kompilace C++ -> strojový kód.Proč? Ale vážně.
Je to spíše naopak - JIT může použít optimalizace na základě aktuálních informací. Tedy například konkrétní typ procesoru, ale především na základě reálných informací o běhu. Typicky může nejčastěji volané metody vložit (inline), smyčky částečně (nebo úplně) rozvinout a podobně.V tom máš samozřejmě pravdu.
Vážně? To jsou věci. Osobně bych určitě nechtěl psát generátor cílového kódu pro procesor, který oplývá instrukcí hledání kořene polynomu, nebo jaká to byla :)Mě by taky zajímalo, jak by se Java rychlostně vypořádala třeba s tím, kdyby C++ začalo používat vektorové instrukce v procesoru. Jestli vůbec Java je schopná třeba optimalizovat stejným způsobem.Viz výše - proč by ne? Dokonce to AFAIK některé JIT používají, ale nevím v jakém je to stavu - a hledat to nebudu, raději půjdu spát.
Proč? Bežné procesory v PC obsahují různé instrukce typu proveď stejný výpočet třeba se čtyřmi, nebo osmi čísly najednou. Výborné na počítání s vektory a maticemi, výsledkem je obrovské zvýšení výkonu. Ale to je asi pro Javu nevyužitelné, protože Java nemá potřebné datové typy.Napsal jsem to blbě, už mi to moc nemyslelo :) Měl jsem na mysli vlastně to, že bych nechtěl psát optimalizátor, který bude v kódu běžného (mezi)jazyka bez speciálních konstrukcí hledat místa, kde by bylo možné takové instrukce použít. Nepochybuju o tom, že takové překladače existují (zmíněno níže v diskusi), ale na můj vkus je to hodně drsná matematika. Pokud jde o Javu, přijde mi taky trochu líto, i když bych to nejspíš nevyužil, že JSR 83 leží ladem (aspoň se mi zdá), tam by se nějaká kouzla dělat dala.
Jeden důvod bych viděl: úspěšnost generování optimálního cílového kódu závisí mj. na tom, jaké máš o programu informace. V nějakém mezikódu (bajtkód či cokoliv jiného) jich určitě máš míň, než třeba v abstraktním syntaktickém stromu (proto třeba překladač Flexu negeneruje v průběhu činnosti žádný mezikód, pokud si to dobře pamatuju :) ). To je jediné, co mne napadá.Pokud ovšem nepovažujeme AST za mezikód
Pokud ovšem nepovažujeme AST za mezikódJasně že AST je svým způsobem mezikód, to je otázka jenom terminologická. Ale je to mezikód s největší vyjadřovací schopností :)
Mimochodem, stejným způsobem snad má fungovat i nejlepší existující kompilátor pro C/C++, a to od Intelu. Přeložíte program a necháte chvíli běžet program, on ho otestuje, zjistí spoustu profilovacích, statistických a jiných informací. A pak ho kompilátor přeloží znovu, ale s použitím zjištěných informací a vytvoří tak výrazně rychlejší program. A dokážu si představit, že taková binárka jde otestovat podstatně lépe, než to, co zvládne JIT.To umi i gcc, ne? -fprofile-generate a -fprofile-use
Nevím, pořád slyším, jaké zázraky zvládne JIT, ale pořád mi něco říká, že C/C++ kompilátor musí fungovat lépe, protože má na analýzu daleko více času, než kolik času může analýze věnovat JIT.Už jsem to sice psal, ale znova: zázrak je, že to vůbec funguje tak dobře, že se o tom zde můžeme bavit. Zázrak je ten obrovský pokrok, jaký se za posledních pár let udělal.
Vždyť některé velké C/C++ projekty se mohou překládat i hodiny! Pochybuji, že by si takový JIT kompilátor mohl dovolit řekněme hodinu analyzovat kód, aby dělal optimalizaci. Navíc mi přijde, že prostě kompilátor C/C++ má v zásadě více informací k optimalizaci, než má JIT.To se většinou řeší tak, že se ze začátku metody přeloží bez optimalizací ("expanze pseudoinstrukcí") a až teprve postupně se používané metody kompilují optimalizovaně. Kompilovat se obecně může mnohokrát, podle potřeby (už kvůli možnosti dynamicky přidávat třídy).
Rychlost je jen jednim ukazatelem kvality. Rychlost aplikace se muze ladit postupne, pokud to umoznuje navrh a zakaznik. C++ ma trpi schizmatem prebujelosti konceptu, do jiste miry je to dany jeho minulosti a pozdnim standardem (BTW se mrknete do zdrojaku Fluxboxu). Pak na zacatecnika pusobi jako soubor vseho mozneho, kde lze delat vse mozne, vsemi zpusoby - no neni to nadhera - moznost vyberu. Pak uz jen chybi, aby si uzivatel osvojil API, ktere vyhovuje jeho podminkam.
Je dobre, ze je tu s nami tolik zajimavych jazyku a pristupu, pak se nedostaneme tak jednoduse do slepe ulicky.
Asi nejlepsi by bylo se blize zajimat o jazyk z hlediska pracovnich prilezitosti a ne rychlosti. "Jasne super, takze kde jsme to naposled skoncili s tim Visual Basicem":). Prave z tohoto hlediska mi vysel zajimavejsi jazyk C++, protoze se v nem proste dela vic zajimavych projektu (studoval jsem biokybernetiku). C++ je pestry, pak i jeho uplatneni je pestry. Ale vcera jsem se dival na projekt Nutch a ten je myslim v Jave, mrknete se.
Jak pestry? Asi takhle. Prevod cisel serazeno od nejrychlejsiho zpusobu po nejpomalejsi, od nejmene bezpecneho k bezpecnemu, od specifickeho k obecnemu reseni - jen si vybrat podle potreby:
C:
atoi, atof, atol
stl:
template<typename T> inline T convertToFrom( const std::string& fromString ) { T toT; std::istringstream tmpStream(fromString); char c; if( !(tmpStream >> toT) || tmpStream.get(c)) throw std::out_of_range( "convertToFrom is std::out_of_range." ); return toT; }
stl,boost:
T i = boost::lexical_cast<T>("123");
Tak se nám tu rozhořela zajímavá hádka o to jestli se dá při dvoukrokovém překladu dosáhnout lepší optimalizace nebo ne. Zajímavé je, že si diskutující neuvědomili dvě důležité skutečnosti.
Z toho nám zdánlivě Java vychází jako vítěz. Její optimalizace jsou jednodužší a tak se v nich dělá méně chyb, dají se lépe matematicky popsat, rychleji se vyvýjí atd. Jenže už to tu jednou zaznělo, Java nutí programátora dělat spoustu zbytečností a ne všechny ty zbytečnosti optimalizace může odstranit. Ve výsledku je to asi tak, že když programátor v C/C++ ví co dělá a autoři překladače neudělali chybu, by Java neměla být rychlejší. Takže na otázku co je rychlejší C/C++ nebo Java bych odpověděl: optimalizoavný Forth. Jeho kód zabere míň místa na disku, zabere míň paměti při běhu a ještě ke všemu se stejně dobře optimalizuje jako Java
Dvoukrokový překlad se často používá i u C/C++ (gcc určitě). To je otázka. GCC používá vícekrokový překlad a pokud jsem pochopil, je to značnou příčinou jeho problémů. Osobně považuji koncepci GCC za jednou z příčin, proč špatně optimalizuje. Byl bych rád, kdybychom se bavili o dobrých překladačích C/C++, a ne o GCC. Navrhuji bavit se o Intelovských, nebo přinejhorším alespoň o Microsoftích kompilátorech C/C++, které optimalizují podstatně lépe a úspěšněji, než GCC.To, že si na ten mezikód nemůžeš šáhnout, ještě neznamená, že se nepřekládá také vícekrokově. Abych pravdu řek, nevím jak by jste chtěl dělat jakékoli optimalizace bez mezikroku sémantického grafu. Jinými slovy, jakýkoli C/C++ překladač, který provádí optimalizaci, musí ten mezikrok udělat. To že je jen u gcc vidět navenek nic neznamená.
Překladem Java->JavaByteCode vzniká jednodužší jazyk než v prvním kroku překladu C/C++. Ano, ale také se překladem Java->JavaByteCode ztrácejí některé informace, které by bylo možné využít k lepší optimalizaci.A jaká informace se ztrácí. Kde se ztrácí? Něco bližšího by k tomu nebylo? Jak už jsem jednou napsal, s výjimkou neoptimalizujících překladačů všechny C/C++ překladače ten mezikrok udělat musí. Kód zásobníkového počítače má právě tu výhodu, že se velmi snadno transformuje do sémantického grafu a pak se nad ním dělají optimalizace. Je pochopitelné, že ten graf musí dělat přesně to samé, co chtěl básník/programátor tím kódem říct. Nic se nesmí ztratit! Problém Javy je, právě to, že básníka nutí říct víc než musí a překladem do ByteCode se z toho nic neztratí. Teprve optimalizace některé zbytečnosti může vynechat, což je pochopitelně práce navíc. Dtto je to s C/C++, ale tam je toho navíc míň.
Rozdíl je ten, že Java bytekód neumožnuje zohlednit naprosto žádnou skutečnost týkající se cílové platformyAčkoli překladačům rozumím asi stejně jako ryba horolezectví, neodpustím si otázku: nemá právě tohle dělat JIT?
Určitě také záleží na optimalizačních schopnostech toho kterého kompilátoru, ale za obdobných podmínek poběží nativní aplikace vždy rychleji.
Schválně jsem si zkusil vygooglit pár testů, kde javovská aplikace údajně běžela rychleji. Sám jsem si přepsal do ekvivalentního kódu v C++ (bez nějakých prasečinek) a výsledek ? O 20 až 250% kratší čas. Potvrdilo se i při zvýšení poštu iterací. GCC 3.3.6, Java 1.5.0 .
O 20 až 250% kratší čas.Gratuluji ke stvoření stroje času. Cestovat do minulosti bych také chtěl - nebo abych měl již dnes dokončenou kompilaci, kterou spustím zítra večer
Správně mělo být samozřejmě O 20 až 25% kratší čas
Sorry
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.