Programiranje

Optimizacija delovanja JVM, 2. del: Prevajalniki

Prevajalniki Java imajo osrednje mesto v tem drugem članku v seriji za optimizacijo delovanja JVM. Eva Andreasson predstavlja različne vrste prevajalnikov in primerja rezultate uspešnosti odjemalskega, strežniškega in stopenjskega prevajanja. Zaključi s pregledom običajnih optimizacij JVM, kot so odprava mrtvih kod, vdelava in optimizacija zanke.

Prevajalnik Java je vir znane neodvisnosti platforme Java. Razvijalec programske opreme napiše najboljšo aplikacijo Java, ki jo lahko, nato pa prevajalnik v zakulisju ustvari učinkovito in dobro izvedbeno kodo za predvideno ciljno platformo. Različne vrste prevajalnikov ustrezajo različnim potrebam aplikacij, kar daje posebne želene rezultate. Bolj ko boste razumeli prevajalnike, kako delujejo in kakšne vrste so na voljo, bolj boste lahko optimizirali delovanje aplikacije Java.

Ta drugi članek v Optimizacija delovanja JVM serija poudarja in pojasnjuje razlike med različnimi prevajalniki navideznih strojev Java. Razpravljal bom tudi o nekaterih pogostih optimizacijah, ki jih uporabljajo JIT-jevi prevajalniki za Javo. (Glejte "Optimizacija delovanja JVM, 1. del" za pregled JVM in uvod v serijo.)

Kaj je prevajalnik?

Preprosto rečeno a prevajalnik za vhod vzame programski jezik in za izhod ustvari izvršljiv jezik. Eden od splošno znanih prevajalnikov je javac, ki je vključen v vse standardne razvojne komplete Java (JDK). javac za vhod vzame kodo Java in jo prevede v bajtno kodo - izvršljivi jezik za JVM. Bytecode se shrani v datoteke .class, ki se ob zagonu Java procesa naložijo v izvajalno okolje Java.

Bytecode-a ni mogoče prebrati s standardnimi CPU-ji in ga je treba prevesti v jezik navodil, ki ga lahko razume osnovna izvedbena platforma. Komponenta v JVM, ki je odgovorna za prevajanje bajtkode v izvedljiva navodila platforme, je še en prevajalnik. Nekateri prevajalniki JVM obravnavajo več stopenj prevajanja; na primer prevajalnik lahko ustvari različne ravni vmesne predstavitve bajtkode, preden se ta spremeni v dejanska strojna navodila, zadnji korak prevajanja.

Bytecode in JVM

Če želite izvedeti več o bytecode in JVM, glejte "Osnove bytecode" (Bill Venners, JavaWorld).

Z agnotičnega vidika platforme želimo ohraniti kodo neodvisno od platforme, kolikor je le mogoče, tako da je zadnja stopnja prevajanja - od najnižje predstavitve do dejanske strojne kode - korak, ki zaklene izvedbo na procesorsko arhitekturo določene platforme. . Najvišja stopnja ločitve je med statičnimi in dinamičnimi prevajalniki. Od tam imamo na voljo možnosti, odvisno na to, na katero ciljno okolje izvajamo, katere rezultate uspešnosti želimo in katere omejitve virov moramo izpolniti. Na kratko sem razpravljal o statičnih in dinamičnih prevajalnikih v 1. delu te serije. V naslednjih razdelkih bom razložil nekaj več.

Statična vs dinamična kompilacija

Primer statičnega prevajalnika je prej omenjeni javac. Pri statičnih prevajalnikih se vhodna koda interpretira enkrat, izhodna izvedbena datoteka pa je v obliki, ki bo uporabljena ob zagonu programa. Če ne spremenite prvotnega vira in ne prevedete kode (z uporabo prevajalnika), bo rezultat vedno enak; to pa zato, ker je vhod statični vhod, prevajalnik pa statični prevajalnik.

V statičnem prevajanju naslednja koda Java

statični int add7 (int x) {return x + 7; }

bi povzročilo nekaj podobnega tej bajtkodi:

iload0 bipush 7 iadd ireturn

Dinamični prevajalnik dinamično prevaja iz enega jezika v drugega, kar pomeni, da se to zgodi med izvajanjem kode - med izvajanjem! Dinamična kompilacija in optimizacija dajeta čas izvajanja prednost, da se lahko prilagodijo spremembam v obremenitvi aplikacije. Dinamični prevajalniki so zelo primerni za izvajanje Java, ki se običajno izvaja v nepredvidljivih in nenehno spreminjajočih se okoljih. Večina JVM uporablja dinamični prevajalnik, kot je prevajalnik Just-In-Time (JIT). Ulov je v tem, da dinamični prevajalniki in optimizacija kode včasih potrebujejo dodatne podatkovne strukture, nit in CPU vire. Bolj kot je napredovala optimizacija ali analiziranje konteksta bajt-kode, več virov porabi prevajanje. V večini okolij so režijski stroški še vedno zelo majhni v primerjavi s pomembnim povečanjem zmogljivosti izhodne kode.

Sorte JVM in neodvisnost platforme Java

Vsem implementacijam JVM je skupno nekaj, to je njihov poskus, da bajt kodo aplikacije pretvori v strojna navodila. Nekateri JVM interpretirajo kodo aplikacije pri nalaganju in uporabljajo števce zmogljivosti, da se osredotočijo na "vročo" kodo. Nekateri JVM preskočijo interpretacijo in se zanašajo samo na kompilacijo. Intenzivnost prevajanja virov je lahko večji zadetek (zlasti za aplikacije na strani odjemalca), omogoča pa tudi naprednejše optimizacije. Za več informacij glejte Viri.

Če ste začetnik Jave, se vam bodo zapletenosti JVM veliko zapletle. Dobra novica je, da vam v resnici ni treba! JVM upravlja prevajanje in optimizacijo kode, tako da vam ni treba skrbeti za strojna navodila in optimalen način pisanja programske kode za osnovno arhitekturo platforme.

Od bajtne kode Java do izvedbe

Ko je vaša koda Java zbrana v bajt kodo, so naslednji koraki prevedba navodil bajtkode v strojno kodo. To lahko stori tolmač ali prevajalnik.

Tolmačenje

Najenostavnejša oblika sestavljanja bajt kod se imenuje interpretacija. An tolmač preprosto poišče navodila za strojno opremo za vsako navodilo bytecode in jih pošlje, da jih CPU izvede.

Lahko bi si mislili interpretacija podobno kot pri uporabi slovarja: za določeno besedo (navodilo bytecode) obstaja natančen prevod (navodilo strojne kode). Ker tolmač naenkrat prebere in takoj izvede eno ukazno bajto kodo, ni možnosti za optimizacijo nad naborom ukazov. Tolmač mora interpretacijo opraviti tudi vsakič, ko se prikliče bajtoda, zaradi česar je precej počasna. Interpretacija je natančen način izvajanja kode, vendar neoptimiziran nabor izhodnih navodil verjetno ne bo najuspešnejše zaporedje za procesor ciljne platforme.

Kompilacija

A prevajalnik po drugi strani pa v izvajalno okolje naloži celotno kodo, ki jo je treba izvesti. Ko prevaja bajtno kodo, ima možnost pogledati celoten ali delni kontekst izvajanja in sprejeti odločitve o tem, kako zares prevesti kodo. Njene odločitve temeljijo na analizi kodnih grafov, kot so različne veje izvajanja navodil in podatki o kontekstu izvajanja.

Ko je zaporedje bajt kod prevedeno v nabor ukazov strojne kode in je mogoče za ta nabor narediti optimizacije, se nadomestni niz ukazov (npr. Optimizirano zaporedje) shrani v strukturo, imenovano predpomnilnik kode. Naslednjič, ko se izvede bajtoda, se lahko predhodno optimizirana koda takoj nahaja v predpomnilniku kode in uporabi za izvajanje. V nekaterih primerih lahko števec zmogljivosti sproži prejšnjo optimizacijo in jo preglasi, v tem primeru bo prevajalnik zagnal novo zaporedje optimizacije. Prednost predpomnilnika kode je v tem, da lahko nastali nabor ukazov izvedete naenkrat - ni potrebe po interpretativnem iskanju ali prevajanju! To pospeši čas izvajanja, zlasti za programe Java, kjer se iste metode pokličejo večkrat.

Optimizacija

Skupaj z dinamičnim prevajanjem pride priložnost za vstavljanje števcev zmogljivosti. Prevajalnik lahko na primer vstavi števec uspešnosti za štetje vsakič, ko je bil poklican blok bajt-kode (npr., ki ustreza določeni metodi). Prevajalniki uporabljajo podatke o tem, kako "vroča" je določena bajtoda, da določijo, kje v optimizaciji kode bo najbolj vplival na delujočo aplikacijo. Podatki o profiliranju med izvajanjem omogočajo prevajalniku, da sproti sprejme bogat nabor odločitev o optimizaciji kode, kar dodatno izboljša zmogljivost izvajanja kode. Ko so na voljo bolj izpopolnjeni podatki o profiliranju kode, jih lahko uporabimo za dodatne in boljše odločitve o optimizaciji, na primer: kako bolje zaporediti navodila v prevedenem jeziku, ali zamenjati niz navodil z bolj učinkovitimi nabori ali celo ali naj odpravi odvečne operacije.

Primer

Upoštevajte kodo Java:

statični int add7 (int x) {return x + 7; }

To bi lahko statično sestavil javac na bajt kodo:

iload0 bipush 7 iadd ireturn

Ko se metoda pokliče, se blok bajtkode dinamično prevede v strojna navodila. Ko števec zmogljivosti (če je prisoten za blok kode) doseže prag, se lahko tudi optimizira. Končni rezultat bi lahko bil videti kot naslednji nabor strojnih navodil za določeno izvršilno platformo:

Lea Rax, [rdx + 7] ret

Različni prevajalniki za različne aplikacije

Različne aplikacije Java imajo različne potrebe. Dolgotrajne poslovne aplikacije na strani strežnika bi lahko omogočile več optimizacij, manjše aplikacije na strani odjemalca pa bodo morda potrebovale hitro izvedbo z minimalno porabo virov. Upoštevajmo tri različne nastavitve prevajalnika in njihove prednosti in slabosti.

Prevajalniki na strani odjemalca

Znan optimizacijski prevajalnik je C1, prevajalnik, ki je omogočen prek -klijent Možnost zagona JVM. Kot pove že njegovo zagonsko ime, je C1 prevajalnik na strani odjemalca. Zasnovan je za odjemalske aplikacije, ki imajo na voljo manj virov in so v mnogih primerih občutljive na čas zagona aplikacije. C1 za profiliranje kode uporablja števce zmogljivosti, ki omogočajo preproste, razmeroma nevsiljive optimizacije.

Prevajalniki na strežniški strani

Za dolgotrajne aplikacije, kot so na primer strežniške poslovne aplikacije Java, odjemalski prevajalnik morda ne bo dovolj. Namesto tega bi lahko uporabili prevajalnik na strani strežnika, kot je C2. C2 je običajno omogočen z dodajanjem možnosti zagona JVM -strežnik v zagonsko ukazno vrstico. Ker se pričakuje, da se bo večina programov na strežniški strani izvajala dlje časa, omogočanje C2 pomeni, da boste lahko zbrali več profilirnih podatkov, kot bi jih imeli s kratkotrajno majhno odjemalsko aplikacijo. Tako boste lahko uporabili naprednejše optimizacijske tehnike in algoritme.

Nasvet: Ogrejte prevajalnik na strani strežnika

Za razmestitve na strežniški strani lahko traja nekaj časa, preden je prevajalnik optimiziral začetne "vroče" dele kode, zato razmestitve na strežniški strani pogosto zahtevajo fazo "ogrevanja". Pred izvajanjem kakršnega koli merjenja zmogljivosti pri uvajanju na strani strežnika se prepričajte, da je vaša aplikacija dosegla stabilno stanje! Če prevajalniku omogočite dovolj časa za pravilno prevajanje, vam bo to koristilo! (Za več informacij o ogrevanju prevajalnika in mehaniki profiliranja glejte članek JavaWorld »Oglejte si prevajalnik HotSpot«.)

Prevajalnik strežnika upošteva več profiliranih podatkov kot prevajalnik na strani odjemalca in omogoča bolj zapleteno analizo vej, kar pomeni, da bo upošteval, katera pot optimizacije bi bila bolj koristna. Več razpoložljivih podatkov za profiliranje daje boljše rezultate uporabe. Za obsežnejše profiliranje in analizo je seveda treba prevajalniku nameniti več sredstev. JVM z omogočenim C2 bo uporabil več niti in več ciklov CPU, zahteval večji predpomnilnik kod itd.

Stopnična kompilacija

Ticasta kompilacija združuje prevajanje na strani odjemalca in strežnika. Azul je prvič dal na voljo stopenjsko kompilacijo v svojem Zing JVM. Pred kratkim (od Jave SE 7) jo je sprejel Oracle Java Hotspot JVM. Tiered compilation izkorišča prednosti odjemalskega in strežniškega prevajalnika v vašem JVM. Odjemalski prevajalnik je najbolj aktiven med zagonom aplikacije in obravnava optimizacije, ki jih sprožijo nižji pragovi števca zmogljivosti. Prevajalnik na strani odjemalca vstavi tudi števce zmogljivosti in pripravi nabore ukazov za naprednejše optimizacije, ki jih bo kasneje obravnaval prevajalnik na strani strežnika. Večstopenjsko prevajanje je zelo učinkovit način profiliranja, ker je prevajalnik sposoben zbirati podatke med dejavnostjo prevajalnika z majhnim učinkom, kar lahko kasneje uporabimo za naprednejše optimizacije. Ta pristop daje tudi več informacij, kot jih boste dobili samo z uporabo interpretiranih števcev profilov kode.

Shema grafikona na sliki 1 prikazuje razlike v zmogljivosti med čisto interpretacijo, odjemalsko, strežniško in stopnjasto kompilacijo. Os X prikazuje čas izvedbe (časovna enota) in učinkovitost osi Y (ops / časovna enota).

Slika 1. Razlike v zmogljivosti med prevajalniki (kliknite za povečavo)

V primerjavi s čisto interpretirano kodo uporaba prevajalnika na strani odjemalca povzroči približno 5 do 10-krat boljšo zmogljivost izvajanja (v operacijskih sistemih / s), s čimer se izboljša zmogljivost aplikacije. Razlike v dobičku so seveda odvisne od tega, kako učinkovit je prevajalnik, katere optimizacije so omogočene ali izvedene in (v manjši meri), kako dobro zasnovana je aplikacija glede na ciljno platformo izvajanja. Slednje je res nekaj, česar razvijalcu Jave ne bi bilo treba skrbeti.

V primerjavi s prevajalnikom na strani odjemalca prevajalnik na strani strežnika običajno poveča zmogljivost kode za merljivih 30 do 50 odstotkov. V večini primerov bo to izboljšanje učinkovitosti uravnotežilo dodatne stroške virov.

$config[zx-auto] not found$config[zx-overlay] not found