Výkon Java - Java performance
Při vývoji softwaru byl programovací jazyk Java historicky považován za pomalejší než nejrychlejší jazyky zadané 3. generace, jako jsou C a C ++ . Hlavním důvodem je odlišný jazykový design, kde po kompilaci běží programy Java na virtuálním stroji Java (JVM), nikoli přímo na procesoru počítače jako nativní kód , stejně jako programy C a C ++. Výkon byl předmětem obav, protože mnoho podnikového softwaru bylo napsáno v Javě poté, co se jazyk rychle stal populárním na konci 90. let a na počátku 2000.
Od konce 90. let se rychlost provádění programů Java výrazně zlepšila zavedením kompilace just-in-time (JIT) (v roce 1997 pro Java 1.1 ), přidáním jazykových funkcí podporujících lepší analýzu kódu a optimalizací v JVM (například jako HotSpot, který se stal výchozím pro Sun JVM v roce 2000). Bylo také prozkoumáno hardwarové provedení bajtového kódu Java, jaké nabízí společnost ARM's Jazelle , aby nabídlo významné vylepšení výkonu.
Výkon z bytecode Java zkompilovaný Java program závisí na tom, jak optimálně jsou jeho dané úkoly řízeny hostitelským Java Virtual Machine (JVM), a jak dobře JVM využívá vlastnosti počítačového hardwaru a operačním systému (OS) při tom. Jakýkoli test výkonu Java nebo srovnání tedy musí vždy hlásit verzi, dodavatele, OS a hardwarovou architekturu použitého JVM. Podobným způsobem bude výkon ekvivalentního nativně kompilovaného programu záviset na kvalitě jeho generovaného strojového kódu, takže test nebo srovnání musí také hlásit název, verzi a dodavatele použitého kompilátoru a jeho aktivované direktivy optimalizace kompilátoru .
Metody optimalizace virtuálního stroje
Mnoho optimalizací v průběhu času zlepšilo výkon JVM. Přestože Java byla často prvním virtuálním strojem, který je úspěšně implementoval, často se používaly i na jiných podobných platformách.
Just-in-time kompilace
Časné JVM vždy interpretovaly bajtové kódy Java . To mělo v průměrných aplikacích velký trest za výkon mezi faktorem 10 a 20 pro Javu oproti C. Abychom tomu zabránili, byl do Java 1.1 zaveden kompilátor just-in-time (JIT). Kvůli vysokým nákladům na kompilaci byl v prostředí Java 1.2 zaveden přidaný systém s názvem HotSpot, který byl v prostředí Java 1.3 nastaven jako výchozí. Pomocí tohoto rámce virtuální stroj Java nepřetržitě analyzuje výkon programu na horká místa, která jsou prováděna často nebo opakovaně. Ty jsou poté zaměřeny na optimalizaci , což vede k vysokému výkonu s minimem režie pro méně kritický kód. Některá měřítka tímto způsobem ukazují 10násobné zvýšení rychlosti. Z důvodu časových omezení však kompilátor nemůže plně optimalizovat program a výsledný program je tedy pomalejší než alternativy nativního kódu.
Adaptivní optimalizace
Adaptivní optimalizace je metoda v počítačové vědě, která provádí dynamickou rekompilaci částí programu na základě aktuálního profilu provádění. S jednoduchou implementací může adaptivní optimalizátor jednoduše provést kompromis mezi instrukcemi pro kompilaci a interpretaci just-in-time. Na jiné úrovni může adaptivní optimalizace využívat podmínky místních dat k optimalizaci vzdálených poboček a využívat inline expanzi.
Java virtual machine jako HotSpot může také deoptimize kód dříve JITed. To umožňuje provádění agresivních (a potenciálně nebezpečných) optimalizací, přičemž je stále možné později deoptimalizovat kód a vrátit se na bezpečnou cestu.
Sběr odpadu
Virtuální stroje Java (JVMs) 1.0 a 1.1 používaly sběrač značek , který mohl fragmentovat hromadu po uvolnění paměti. Počínaje verzí Java 1.2 se prostředí JVM změnilo na generační sběratel , který má mnohem lepší chování při defragmentaci. Moderní JVM používají celou řadu metod, které dále zlepšily výkon uvolňování paměti .
Další optimalizační metody
Komprimované Jejda
Komprimované Oops umožňuje prostředí Java 5.0+ adresovat až 32 GB haldy s 32bitovými odkazy. Java nepodporuje přístup k jednotlivým bajtům, pouze k objektům, které jsou ve výchozím nastavení zarovnány na 8 bajtů. Z tohoto důvodu budou nejnižší 3 bity odkazu na haldu vždy 0. Snížením rozlišení 32bitových odkazů na 8 bajtových bloků lze adresovatelný prostor zvýšit na 32 GB. To výrazně snižuje využití paměti ve srovnání s použitím 64bitových odkazů, protože Java používá odkazy mnohem více než některé jazyky, jako je C ++. Java 8 podporuje větší zarovnání, jako je 16bajtové zarovnání, které podporuje až 64 GB s 32bitovými odkazy.
Rozdělení ověření bytového kódu
Před provedením třídy ověří Sun JVM své bajtové kódy Java (viz ověřovač bajtových kódů ). Toto ověření se provádí líně: bajtkódy tříd se načítají a ověřují pouze tehdy, když je konkrétní třída načtena a připravena k použití, a nikoli na začátku programu. Jelikož však knihovny tříd Java jsou také běžnými třídami Java, musí být při jejich použití také načteny, což znamená, že doba spuštění programu Java je často delší než například u programů C ++ .
V JVM se od verze Java 6 používá metoda s názvem ověření v mezičase , poprvé představená na platformě Java, Micro Edition (J2ME) . Rozdělí ověření bytového kódu Java na dvě fáze:
- Design-time - při kompilaci třídy ze zdroje do bytecode
- Runtime - při načítání třídy.
V praxi tato metoda funguje tak, že zachycuje znalosti, které má kompilátor Java, o toku třídy, a anotuje bajtkódy kompilované metody s přehledem informací o toku třídy. Díky tomu není ověření běhu znatelně méně složité, ale umožňuje některé zkratky.
Analýza úniků a zhrubnutí zámku
Java je schopna spravovat multithreading na jazykové úrovni. Vícevláknový proces je metoda, která umožňuje programům provádět více procesů současně, čímž produkuje rychlejší programy v počítačových systémech s více procesory nebo jádry. Také aplikace s více vlákny může zůstat citlivá na vstup, a to i při provádění dlouhotrvajících úkolů.
Programy, které používají vícevláknové zpracování, se však musí zvlášť starat o objekty sdílené mezi vlákny, zamykat přístup ke sdíleným metodám nebo blokovat, pokud jsou používány jedním z vláken. Uzamčení bloku nebo objektu je časově náročná operace kvůli povaze podkladové operace na úrovni operačního systému (viz řízení souběžnosti a granularita zámku ).
Jelikož knihovna Java neví, které metody budou použity více než jedním vláknem, standardní knihovna v případě potřeby vždy uzamkne bloky v prostředí s více vlákny.
Před verzí Java 6 virtuální stroj vždy zamkl objekty a bloky, když o to program požádal, i když nehrozilo, že bude objekt upraven dvěma různými vlákny najednou. Například v tomto případě byl místní vector uzamčen před každou operací přidání, aby bylo zajištěno, že nebude upraven jinými vlákny (vektor je synchronizován), ale protože je pro metodu přísně místní, je to zbytečné:
public String getNames() {
Vector<String> v = new Vector<>();
v.add("Me");
v.add("You");
v.add("Her");
return v.toString();
}
Počínaje jazykem Java 6 jsou bloky kódu a objekty uzamčeny pouze v případě potřeby, takže ve výše uvedeném případě by virtuální stroj vůbec nezamkl objekt Vector.
Od verze 6u23 zahrnuje Java podporu pro únikovou analýzu.
Zaregistrovat vylepšení přidělení
Před Javou 6 byla alokace registrů na klientském virtuálním stroji velmi primitivní (nežili přes bloky ), což byl problém v designech CPU, které měly k dispozici méně procesorových registrů , jako v x86s . Pokud pro operaci nejsou k dispozici žádné další registry, kompilátor musí zkopírovat z registru do paměti (nebo do paměti k registraci), což zabere nějaký čas (přístup k registrům je podstatně rychlejší). Nicméně, server, virtuální stroj používal barevný graf alokátor a neměl tento problém.
Optimalizace alokace registrů byla zavedena v JDK 6 společnosti Sun; poté bylo možné použít stejné registry napříč bloky (pokud byly použity), což omezovalo přístupy do paměti. To vedlo v některých benchmarcích k hlášenému nárůstu výkonu přibližně o 60%.
Sdílení dat třídy
Sdílení dat třídy (nazývané CDS by Sun) je mechanismus, který snižuje dobu spouštění aplikací Java a také snižuje paměťovou stopu . Když je nainstalováno JRE , instalační program načte sadu tříd ze systémového souboru JAR (soubor JAR obsahující celou knihovnu tříd Java s názvem rt.jar) do soukromé interní reprezentace a tuto reprezentaci vypíše do souboru s názvem a "sdílený archiv". Během následných vyvolání JVM je tento sdílený archiv mapován do paměti , což šetří náklady na načítání těchto tříd a umožňuje sdílení většiny metadat JVM pro tyto třídy mezi více procesy JVM.
Odpovídající zlepšení doby spuštění je u malých programů patrnější.
Historie vylepšení výkonu
Kromě zde uvedených vylepšení přineslo každé vydání Javy mnoho vylepšení výkonu v rozhraní JVM a Java Application Programming Interface (API).
JDK 1.1.6: První kompilace just-in-time ( kompilátor JIT společnosti Symantec )
J2SE 1.2: Použití generačního kolektoru .
J2SE 1.3: Just-in-time kompilaci by HotSpot .
J2SE 1.4: Zde naleznete Sun přehled zlepšení výkonu mezi verzemi 1.3 a 1.4.
Java SE 5.0: Sdílení dat třídy
Java SE 6:
Další vylepšení:
- Vylepšení rychlosti kanálu Java OpenGL Java 2D
- Výkon Java 2D se také výrazně zlepšil v prostředí Java 6
Viz také „Přehled Sun o vylepšení výkonu mezi prostředími Java 5 a Java 6“.
Aktualizace Java SE 6 10
- Java Quick Starter zkracuje čas spuštění aplikace přednačtením části dat JRE při spuštění OS na diskové mezipaměti .
- Části platformy potřebné k provedení aplikace přístupné z webu, když není nainstalováno JRE, jsou nyní staženy jako první. Plné JRE je 12 MB, pro spuštění typické aplikace Swing stačí stáhnout pouze 4 MB. Zbývající části se poté stáhnou na pozadí.
- Grafický výkon v systému Windows se zlepšil rozsáhlým používáním Direct3D ve výchozím nastavení a pomocí shaderů na grafické procesorové jednotce (GPU) k urychlení složitých operací Java 2D .
Java 7
Pro prostředí Java 7 bylo vydáno několik vylepšení výkonu: Budoucí vylepšení výkonu jsou plánována pro aktualizaci prostředí Java 6 nebo Java 7:
- Poskytovat podporu JVM pro dynamické programovací jazyky podle prototypových prací aktuálně prováděných na stroji Da Vinci (vícejazyčný virtuální stroj),
- Vylepšete stávající knihovnu souběžnosti správou paralelního výpočtu na vícejádrových procesorech,
- Povolte JVM používat klientské i serverové kompilátory JIT ve stejné relaci pomocí metody zvané vrstvené kompilace:
- Klient by byl použit při spuštění (protože je to dobré při startu a pro malé aplikace),
- Server by byly použity pro dlouhodobý provoz aplikace (protože překonává klienta kompilátor pro toto).
- Nahraďte stávající souběžný sběrač odpadků s nízkou pauzou (nazývaný také souběžný sběrač značek (CMS)) novým sběračem s názvem Garbage First (G1), abyste zajistili konzistentní pauzy v průběhu času.
Srovnání s jinými jazyky
Objektivní srovnání výkonu programu Java a ekvivalentního programu napsaného v jiném jazyce, jako je C ++, vyžaduje pečlivě a promyšleně vytvořený test, který porovnává programy dokončující stejné úkoly. Cílová platforma z Java bytecode kompilátor je platforma Java a bytecode je buď interpretován nebo kompilován do strojového kódu do JVM. Ostatní překladači se téměř vždy zaměřují na konkrétní hardwarovou a softwarovou platformu a vytvářejí strojový kód, který během provádění zůstane prakticky nezměněn. Z těchto dvou různých přístupů vyplývají velmi odlišné a obtížně srovnatelné scénáře: statické vs. dynamické kompilace a rekompilace , dostupnost přesných informací o běhovém prostředí a další.
Java je často za běhu kompilována za běhu virtuálním strojem Java , ale může být také kompilována předem , stejně jako C ++. Při kompilaci just-in-time indikují mikro-benchmarky The Computer Language Benchmarks Game o jejím výkonu následující:
- pomalejší než kompilované jazyky jako C nebo C ++ ,
- podobně jako jiné kompilované jazyky just-in-time, jako je C # ,
- mnohem rychlejší než jazyky bez efektivního kompilátoru nativního kódu ( JIT nebo AOT ), jako je Perl , Ruby , PHP a Python .
Rychlost programu
Srovnávací hodnoty často měří výkon malých numericky náročných programů. V některých vzácných programech v reálném životě překonává Java C. Jedním příkladem je měřítko Jake2 (klon Quake II napsaný v Javě překladem původního kódu GPL C). Verze Java 5.0 funguje v některých hardwarových konfiguracích lépe než její protějšek C. I když není specifikováno, jak byla data měřena (například pokud byl použit původní spustitelný soubor Quake II zkompilovaný v roce 1997, což lze považovat za špatné, protože současné kompilátory C mohou dosáhnout lepší optimalizace pro Quake), bere na vědomí, jak stejný zdrojový kód Java může mít obrovské zvýšení rychlosti pouhou aktualizací virtuálního počítače, čeho je nemožné dosáhnout pomocí 100% statického přístupu.
U jiných programů může protějšek C ++ běžet a obvykle běží podstatně rychleji než ekvivalent Java. Srovnávací test provedený společností Google v roce 2011 ukázal faktor 10 mezi C ++ a Javou. Na druhém konci, akademický benchmark provedený v roce 2012 s algoritmem 3D modelování ukázal, že Java 6 JVM je od 1,09 do 1,91 krát pomalejší než C ++ pod Windows.
Některé optimalizace, které jsou možné v Javě a podobných jazycích, nemusí být za určitých okolností v C ++ možné:
- Použití ukazatele ve stylu C může bránit optimalizaci v jazycích, které podporují ukazatele,
- Použití metod únikové analýzy je v C ++ omezené , například proto, že překladač C ++ ne vždy ví, jestli bude objekt v daném bloku kódu upraven kvůli ukazatelům ,
- Java může přistupovat k metodám odvozených instancí rychleji než C ++ může přistupovat k odvozeným virtuálním metodám díky extra vyhledávání virtuální tabulky v C ++. Nevirtuální metody v C ++ však netrpí úzkými místy výkonu v tabulce, a tak vykazují výkon podobný Javě.
JVM je také schopen provádět optimalizace specifické pro procesor nebo inline expanzi . Schopnost deoptimalizace kódu, který již byl zkompilován nebo vložen, mu někdy umožňuje provádět agresivnější optimalizace, než jaké provádějí staticky zadané jazyky, když jsou zahrnuty funkce externí knihovny.
Výsledky mikrobenchmarků mezi Javou a C ++ velmi závisí na tom, které operace jsou porovnávány. Například při porovnání s Java 5.0:
- 32 a 64bitové aritmetické operace, File I / O a zpracování výjimek , mají podobný výkon jako srovnatelné programy C ++
- Výkon operací polí je v C. lepší
- Výkon trigonometrických funkcí je v C mnohem lepší.
- Poznámky
Vícejádrový výkon
Škálovatelnost a výkon aplikací Java na vícejádrových systémech je omezen rychlostí přidělování objektů. Tento efekt se někdy nazývá „zeď přidělení“. V praxi však moderní algoritmy sběrače odpadků používají k provádění sběru odpadků více jader, což do určité míry tento problém zmírňuje. U některých sběratelů se tvrdí, že udržují alokační rychlosti přes gigabajt za sekundu a existují systémy založené na Javě, které nemají problémy se škálováním na několik stovek procesorových jader a hromady o velikosti několika stovek GB.
Automatická správa paměti v Javě umožňuje efektivní využití nezamykatelných a neměnných datových struktur, které je extrémně těžké nebo někdy nemožné implementovat bez nějakého druhu uvolňování paměti. Java nabízí řadu takových struktur na vysoké úrovni ve své standardní knihovně v balíčku java.util.concurrent, zatímco v mnoha jazycích, které se historicky používají pro vysoce výkonné systémy, jako je C nebo C ++, stále chybí.
Čas spuštění
Čas spuštění Java je často mnohem pomalejší než mnoho jazyků, včetně C , C ++ , Perl nebo Python , protože před použitím je třeba načíst mnoho tříd (a především všech tříd z knihoven tříd platformy ).
Ve srovnání s podobnými populárními běhovými časy se u malých programů spuštěných na počítači se systémem Windows čas spuštění jeví jako podobný Mono a trochu pomalejší než .NET .
Zdá se, že velká část doby spuštění je způsobena spíše operacemi vázanými na vstup a výstup (IO) než inicializací JVM nebo načítáním třídy ( samotný datový soubor třídy rt.jar je 40 MB a JVM musí v tomto velkém souboru hledat mnoho dat) . Některé testy ukázaly, že ačkoliv nová metoda ověřování split bytecode zlepšila načítání třídy zhruba o 40%, u velkých programů došlo pouze k 5% vylepšení spouštění.
I když je to malé zlepšení, je to viditelnější u malých programů, které provádějí jednoduchou operaci a poté ji ukončují, protože načítání dat platformy Java může představovat mnohonásobné zatížení operace skutečného programu.
Počínaje aktualizací Java SE 6 Update 10 přichází Sun JRE s Quick Starter, který před spuštěním OS načte data třídy a získává data z mezipaměti disku, nikoli z disku.
Excelsior JET přistupuje k problému z druhé strany. Jeho Startup Optimizer snižuje množství dat, která musí být čtena z disku při spuštění aplikace, a dělá čtení více sekvenční.
V listopadu 2004 byl veřejně vydán Nailgun , „klient, protokol a server pro spouštění programů Java z příkazového řádku, aniž by vznikly režijní náklady při spuštění JVM“. poprvé zavádění možnosti, aby skripty používaly JVM jako démona , pro spouštění jedné nebo více aplikací Java bez režie spouštění JVM. Démon Nailgun je nezabezpečený: „všechny programy jsou spouštěny se stejnými oprávněními jako server“. Tam, kde je potřeba zabezpečení více uživatelů, je Nailgun bez zvláštních opatření nevhodný. Skripty, kde při použití prostředku dominuje spuštění JVM, viz vylepšení výkonu za běhu o jeden až dva řády .
Využití paměti
Využití paměti Java je mnohem vyšší než využití paměti C ++, protože:
- V Javě existuje režie 8 bajtů pro každý objekt a 12 bajtů pro každé pole. Pokud velikost objektu není násobkem 8 bajtů, zaokrouhlí se nahoru na další násobek 8. To znamená, že objekt obsahující jedno bajtové pole zabírá 16 bajtů a potřebuje 4bajtový odkaz. C ++ také přiděluje ukazatel (obvykle 4 nebo 8 bajtů) pro každý objekt, který třída přímo nebo nepřímo deklaruje virtuální funkce .
- Nedostatek aritmetiky adres znemožňuje vytváření paměťově efektivních kontejnerů, jako jsou pevně rozmístěné struktury a XOR propojené seznamy ( projekt OpenJDK Valhalla má tyto problémy zmírnit, i když jeho cílem není zavést aritmetiku ukazatele; to nelze provést v prostředí pro sběr odpadu).
- Na rozdíl od malloc a new se průměrná režie výkonu sběru odpadu asymptoticky blíží nule (přesněji jeden cyklus CPU), jak se zvyšuje velikost haldy.
- Části knihovny tříd Java se musí před spuštěním programu načíst (alespoň třídy používané v programu). To vede k značné režii paměti pro malé aplikace.
- Binární prostředí Java i nativní rekompilace budou obvykle v paměti.
- Virtuální stroj využívá značnou paměť.
- V Javě je složený objekt (třída A, který používá instance B a C) vytvořen pomocí odkazů na přidělené instance B a C. V C ++ je možné se vyhnout nákladům na paměť a výkon těchto typů odkazů, když instance B a / nebo C existuje v A.
Ve většině případů bude aplikace C ++ spotřebovávat méně paměti než ekvivalentní aplikace Java kvůli velké režii virtuálního stroje Java, načítání tříd a automatické změně velikosti paměti. U programů, kde je paměť rozhodujícím faktorem pro výběr mezi jazyky a běhovými prostředími, je nutná analýza nákladů a přínosů.
Trigonometrické funkce
Výkon trigonometrických funkcí je ve srovnání s C špatný, protože Java má přísné specifikace výsledků matematických operací, které nemusí odpovídat základní implementaci hardwaru. Na X87 s pohyblivou řádovou čárkou podskupiny, Java od 1,4 dělá snížení argument pro sin a cos v softwaru, což způsobuje velký výkonu hit pro hodnoty mimo rozsah. JDK (11 a výše) má ve srovnání s JDK 8 významný pokrok v rychlosti vyhodnocení trigonometrických funkcí.
Nativní rozhraní Java
Java Native Interface vyvolá vysoko nad hlavou, což je nákladné překročit hranici mezi kód běžící na JVM a nativní kód. Java Native Access (JNA) poskytuje programům Java snadný přístup k nativním sdíleným knihovnám ( dynamická knihovna (DLL) ve Windows) pouze prostřednictvím kódu Java, bez JNI nebo nativního kódu. Tato funkcionalita je srovnatelná s Windows 'Platform / Invoke a Pythonovými typy. Přístup je dynamický za běhu bez generování kódu. Má to však cenu a JNA je obvykle pomalejší než JNI.
Uživatelské rozhraní
Swing byl vnímán jako pomalejší než nativní sady nástrojů widgetu , protože deleguje vykreslování widgetů na čisté rozhraní Java 2D API . Benchmarky porovnávající výkon Swing versus Standard Widget Toolkit , který deleguje vykreslení na nativní GUI knihovny operačního systému, však nevykazují žádného jasného vítěze a výsledky do značné míry závisí na kontextu a prostředí. Navíc novější rámec JavaFX , který má nahradit Swing, řeší mnoho inherentních problémů Swing.
Použijte pro vysoce výkonné výpočty
Někteří lidé se domnívají, že výkon Java pro vysoce výkonné výpočty (HPC) je obdobou Fortranu ve srovnávacích testech náročných na výpočet, ale že JVM mají stále problémy se škálovatelností pro provádění intenzivní komunikace v mřížkové výpočetní síti.
Vysoce výkonné počítačové aplikace napsané v Javě však získaly srovnávací soutěže. V letech 2008 a 2009 byl klastr založený na Apache Hadoop (open-source vysoce výkonný výpočetní projekt napsaný v Javě) schopen nejrychleji seřadit terabajt a petabyte celých čísel. Hardwarové nastavení konkurenčních systémů však nebylo opraveno.
V programovacích soutěžích
Programy v Javě začínají pomaleji než v jiných kompilovaných jazycích. Některé online soudní systémy, zejména ty, které hostují čínské univerzity, proto používají delší časové limity pro programy Java, aby byly spravedlivé vůči soutěžícím používajícím Java.
Viz také
- Common Language Runtime
- Analýza výkonu
- Procesor Java , vestavěný procesor s nativním spuštěním bajtového kódu Java (například JStik )
- Porovnání Java a C ++
- Java ConcurrentMap