Programmēšana

JVM veiktspējas optimizācija, 2. daļa: Kompilatori

Šajā otrajā JVM veiktspējas optimizācijas sērijas rakstā galvenā uzmanība tiek pievērsta Java kompilatoriem. Eva Andreasson iepazīstina ar dažādām kompilatoru šķirnēm un salīdzina klienta, servera un daudzpakāpju kompilācijas veiktspējas rezultātus. Viņa noslēdz ar pārskatu par kopīgām JVM optimizācijām, piemēram, mirušā koda izslēgšanu, iekļaušanu un cilpu optimizāciju.

Java kompilators ir Java slavenās neatkarības avots. Programmatūras izstrādātājs uzraksta labāko Java lietojumprogrammu, ko viņš vai viņa var, un tad kompilators strādā aiz ainas, lai izveidotu efektīvu un labi izpildītu izpildes kodu paredzētajai mērķa platformai. Dažāda veida kompilatori atbilst dažādām lietojumprogrammu vajadzībām, tādējādi dodot konkrētus vēlamos veiktspējas rezultātus. Jo vairāk jūs saprotat par kompilatoriem, ņemot vērā to darbību un pieejamos veidus, jo vairāk jūs varēsit optimizēt Java lietojumprogrammu veiktspēju.

Šis otrais raksts JVM veiktspējas optimizācija sērija izceļ un izskaidro atšķirības starp dažādiem Java virtuālo mašīnu kompilatoriem. Es arī apspriedīšu dažas parastās optimizācijas, kuras Java ir izmantojuši Just-In-Time (JIT) kompilatori. (JVM pārskatu un ievadu sērijai skatiet sadaļā "JVM veiktspējas optimizācija, 1. daļa".)

Kas ir sastādītājs?

Vienkārši runājot a sastādītājs programmēšanas valodu uztver kā ievadi un kā izvadi izveido izpildāmo valodu. Viens plaši pazīstams kompilators ir javac, kas ir iekļauta visos standarta Java izstrādes komplektos (JDK). javac uzskata Java kodu kā ievadi un pārveido to baitkodā - izpildāmajā valodā JVM. Baitkods tiek saglabāts .class failos, kas tiek ielādēti Java izpildlaikā, kad tiek sākts Java process.

Bite kodu nevar nolasīt standarta procesori, un tas ir jātulko instrukciju valodā, kuru var saprast pamatā esošā izpildes platforma. JVM komponents, kas ir atbildīgs par baitkodu tulkošanu izpildāmās platformas instrukcijās, ir vēl viens kompilators. Daži JVM kompilatori apstrādā vairākus tulkošanas līmeņus; piemēram, kompilators var izveidot dažādus baitkoda starpposma attēlojuma līmeņus, pirms tas pārvēršas par faktiskām mašīnu instrukcijām, kas ir pēdējais tulkošanas solis.

Bite kods un JVM

Ja vēlaties uzzināt vairāk par baitkodu un JVM, skatiet sadaļu "Bytecode pamati" (Bils Venners, JavaWorld).

No platformas-agnostikas viedokļa mēs vēlamies pēc iespējas saglabāt kodu neatkarīgu no platformas, lai pēdējais tulkošanas līmenis - no zemākā attēlojuma līdz faktiskajam mašīnas kodam - būtu solis, kas bloķē izpildi konkrētas platformas procesora arhitektūrā . Augstākais atdalīšanas līmenis ir starp statiskajiem un dinamiskajiem kompilatoriem. Turpmāk mums ir iespējas atkarībā no tā, uz kādu izpildes vidi mēs orientējamies, kādus veiktspējas rezultātus mēs vēlamies un kādi resursu ierobežojumi mums jāievēro. Šīs sērijas 1. daļā es īsi apspriedu statiskos un dinamiskos sastādītājus. Nākamajās sadaļās es paskaidrošu nedaudz vairāk.

Statiskā un dinamiskā kompilācija

Statiskā sastādītāja piemērs ir iepriekš minēts javac. Izmantojot statiskos kompilatorus, ievades kods tiek interpretēts vienu reizi, un izvades izpildāmā versija ir formā, kas tiks izmantota, izpildot programmu. Ja vien neveicat izmaiņas sākotnējā avotā un atkārtoti neapkopojat kodu (izmantojot kompilatoru), izvades rezultāts vienmēr būs tāds pats; tas ir tāpēc, ka ievade ir statiska ievade un kompilators ir statisks kompilators.

Statiskā kompilācijā šāds Java kods

static int add7 (int x) {return x + 7; }

radītu kaut ko līdzīgu šim baitkodam:

iload0 bipush 7 iadd atgriezties

Dinamisks kompilators dinamiski tulko no vienas valodas uz citu, kas nozīmē, ka tas notiek, izpildot kodu - izpildlaika laikā! Dinamiskā kompilēšana un optimizēšana nodrošina izpildlaika priekšrocību, jo tā spēj pielāgoties lietojumprogrammas slodzes izmaiņām. Dinamiskie kompilatori ir ļoti labi piemēroti Java izpildlaikiem, kurus parasti izpilda neparedzamās un pastāvīgi mainīgās vidēs. Lielākā daļa JVM izmanto dinamisku kompilatoru, piemēram, Just-In-Time (JIT) kompilatoru. Pieķeršanās ir tāda, ka dinamiskajiem kompilatoriem un kodu optimizēšanai dažreiz ir nepieciešamas papildu datu struktūras, pavedieni un procesora resursi. Jo progresīvāka ir optimizācija vai baitu kodu konteksta analīze, jo vairāk resursu tiek patērēts kompilācijā. Lielākajā daļā vidējo izmaksu joprojām ir ļoti maz, salīdzinot ar izejas koda ievērojamo veiktspējas pieaugumu.

JVM šķirnes un Java platformas neatkarība

Visām JVM ieviešanām ir viena kopīga iezīme, proti, mēģinājums panākt, lai lietojumprogrammas baitkods tiktu pārveidots mašīnas instrukcijās. Daži JVM interpretē lietojumprogrammas kodu slodzē un izmanto veiktspējas skaitītājus, lai koncentrētos uz “karsto” kodu. Daži JVM izlaiž interpretāciju un paļaujas tikai uz apkopošanu. Kompilācijas resursu intensitāte var būt lielāks trieciens (īpaši attiecībā uz klienta puses lietojumprogrammām), bet tas arī ļauj uzlabot uzlabojumus. Plašāku informāciju skatiet sadaļā Resursi.

Ja esat Java iesācējs, JVM sarežģījumi būs daudz, lai apliktu galvu. Labās ziņas ir tādas, ka jums tas tiešām nav vajadzīgs! JVM pārvalda kodu kompilēšanu un optimizāciju, tāpēc jums nav jāuztraucas par mašīnu instrukcijām un optimālu lietojumprogrammas koda rakstīšanas veidu pamatā esošajai platformas arhitektūrai.

No Java baitkoda līdz izpildei

Kad Java kods ir apkopots baitkodā, nākamās darbības ir tulkot bytecode instrukcijas mašīnkodā. To var izdarīt vai nu tulks, vai kompilators.

Interpretācija

Vienkāršāko baitkodu sastādīšanas veidu sauc par interpretāciju. An tulks vienkārši meklē aparatūras instrukcijas katrai baitkoda instrukcijai un nosūta to izpildei CPU.

Jūs varētu domāt interpretācija līdzīgi kā lietojot vārdnīcu: konkrētam vārdam (baitkoda instrukcijai) ir precīzs tulkojums (mašīnkoda instrukcija). Tā kā tulks lasa un uzreiz izpilda vienu baitkoda instrukciju vienlaikus, nav iespējas optimizēt, izmantojot instrukciju kopu. Tulka tulkošana ir jāveic arī katru reizi, kad tiek izsaukts baitkods, kas to padara diezgan lēnu. Interpretācija ir precīzs koda izpildes veids, taču neoptimizētā izvades instrukciju kopa mērķa platformas procesoram, visticamāk, nebūs visveiksmīgākā secība.

Kompilācija

A sastādītājs no otras puses, izpildīšanas laikā tiek ielādēts viss izpildāmais kods. Tulkojot baitkodu, tam ir iespēja apskatīt visu vai daļēju izpildlaika kontekstu un pieņemt lēmumus par koda patieso tulkošanu. Tās lēmumi ir balstīti uz kodu diagrammu, piemēram, dažādu izpildes zaru instrukciju un izpildlaika konteksta datu, analīzi.

Kad baitkodu secība tiek pārveidota mašīnkodu instrukciju komplektā un var veikt optimizāciju šai instrukciju kopai, aizstājošā instrukciju kopa (piemēram, optimizētā secība) tiek saglabāta struktūrā, ko sauc par koda kešatmiņa. Nākamreiz, kad tiek izpildīts baitkods, iepriekš optimizēto kodu var uzreiz atrast koda kešatmiņā un izmantot izpildei. Dažos gadījumos veiktspējas skaitītājs var iesākt un ignorēt iepriekšējo optimizāciju, tādā gadījumā kompilators palaidīs jaunu optimizācijas secību. Kodu kešatmiņas priekšrocība ir tā, ka iegūto instrukciju kopu var izpildīt uzreiz - nav nepieciešami interpretatīvi meklējumi vai kompilācija! Tas paātrina izpildes laiku, īpaši Java lietojumprogrammām, kurās vienas un tās pašas metodes tiek izsauktas vairākas reizes.

Optimizācija

Kopā ar dinamisku kompilāciju nāk iespēja ievietot veiktspējas skaitītājus. Kompilators, piemēram, varētu ievietot a veiktspējas skaitītājs skaitīt katru reizi, kad tika izsaukts baitkodu bloks (piemēram, atbilstot noteiktai metodei). Sastādītāji izmanto datus par to, cik dots koda kodums ir karsts, lai noteiktu, kur koda optimizācija vislabāk ietekmēs darbojošos lietojumprogrammu. Runtime profilēšanas dati kompilatoram ļauj ātri veikt bagātīgu kodu optimizācijas lēmumu kopumu, vēl vairāk uzlabojot koda izpildes veiktspēju. Kad kļūst pieejami precīzāki kodēšanas profilēšanas dati, tos var izmantot, lai pieņemtu papildu un labākus optimizācijas lēmumus, piemēram: kā labāk sekot instrukcijām apkopotajā valodā, vai instrukciju kopu aizstāt ar efektīvākiem komplektiem vai pat vai likvidēt liekās darbības.

Piemērs

Apsveriet Java kodu:

static int add7 (int x) {return x + 7; }

To varētu statiski apkopot javac baitkodam:

iload0 bipush 7 iadd atgriezties

Ja metodi sauc par baitkoda bloku, tas tiks dinamiski apkopots atbilstoši mašīnas instrukcijām. Kad veiktspējas skaitītājs (ja tāds ir kodu blokam) sasniedz slieksni, tas var arī tikt optimizēts. Galarezultāts varētu izskatīties pēc šādas mašīnu instrukciju kopas noteiktai izpildes platformai:

lea rax, [rdx + 7] ret

Dažādi kompilatori dažādām lietojumprogrammām

Dažādām Java lietojumprogrammām ir dažādas vajadzības. Ilgstošas ​​uzņēmuma servera puses lietojumprogrammas varētu ļaut vairāk optimizēt, savukārt mazākām klienta puses lietojumprogrammām var būt nepieciešama ātra izpilde ar minimālu resursu patēriņu. Apsvērsim trīs dažādus kompilatora iestatījumus un to attiecīgos plusus un mīnusus.

Klienta puses kompilatori

Plaši pazīstams optimizācijas kompilators ir C1, kompilators, kas ir iespējots, izmantojot -klients JVM startēšanas opcija. Kā liecina starta nosaukums, C1 ir klienta puses kompilators. Tas ir paredzēts klienta lietojumprogrammām, kurām ir mazāk pieejamo resursu un kas daudzos gadījumos ir jutīgi pret lietojumprogrammas palaišanas laiku. C1 koda profilēšanai izmanto veiktspējas skaitītājus, lai iespējotu vienkāršu, salīdzinoši neuzkrītošu optimizāciju.

Servera puses kompilatori

Ilgstoši lietojamām lietojumprogrammām, piemēram, servera puses uzņēmuma Java lietojumprogrammām, iespējams, nepietiek ar klienta puses kompilatoru. Tā vietā varētu izmantot servera puses kompilatoru, piemēram, C2. C2 parasti tiek iespējots, pievienojot JVM startēšanas opciju serveris uz startēšanas komandrindu. Tā kā paredzams, ka lielākā daļa servera puses programmu darbosies ilgu laiku, C2 iespējošana nozīmē, ka varēsiet savākt vairāk profilēšanas datu, nekā to darītu, izmantojot īslaicīgi darbojošos vieglo klientu lietojumprogrammu. Tātad jūs varēsiet izmantot uzlabotas optimizācijas metodes un algoritmus.

Padoms: iesildiet servera puses kompilatoru

Servera puses izvietošanai var paiet zināms laiks, līdz kompilators ir optimizējis sākotnējās koda “karstās” daļas, tāpēc servera puses izvietošanai bieži nepieciešama “iesildīšanās” fāze. Pirms jebkāda veida veiktspējas mērīšanas servera puses izvietojumā pārliecinieties, vai jūsu lietojumprogramma ir sasniegusi līdzsvara stāvokli! Atstājot sastādītājam pietiekami daudz laika, lai pareizi sastādītu, tas nāks jūsu labā! (Lai uzzinātu vairāk par kompilatora iesildīšanu un profilēšanas mehāniku, skatiet JavaWorld rakstu "Skatieties, kā iet jūsu HotSpot kompilators.")

Servera kompilators veido vairāk profilēšanas datu nekā klienta puses kompilators, un ļauj veikt sarežģītāku filiāles analīzi, kas nozīmē, ka tas apsvērs, kurš optimizācijas ceļš būtu izdevīgāks. Ja ir pieejami vairāk profilēšanas datu, tiek iegūti labāki lietojuma rezultāti. Protams, lai veiktu plašāku profilēšanu un analīzi, ir nepieciešams tērēt vairāk resursu kompilatoram. JVM ar iespējotu C2 izmantos vairāk pavedienu un vairāk CPU ciklu, būs nepieciešama lielāka koda kešatmiņa utt.

Daudzpakāpju kompilācija

Daudzpakāpju kompilācija apvieno klienta un servera puses kompilāciju. Azul vispirms daudzpakāpju kompilāciju padarīja pieejamu savā Zing JVM. Pavisam nesen (sākot ar Java SE 7) to ir pieņēmis Oracle Java Hotspot JVM. Daudzpakāpju kompilēšana izmanto gan klienta, gan servera kompilatora priekšrocības jūsu JVM. Klienta kompilators ir visaktīvākais lietojumprogrammas startēšanas laikā un apstrādā optimizāciju, ko izraisa zemāki veiktspējas skaitītāja sliekšņi. Klienta puses kompilators arī ievieto veiktspējas skaitītājus un sagatavo instrukciju kopas sarežģītākai optimizācijai, kuras vēlāk servera puses kompilators pievērsīsies. Daudzpakāpju kompilācija ir ļoti resursu ziņā efektīvs profilēšanas veids, jo kompilators mazas ietekmes kompilatora darbības laikā spēj apkopot datus, kurus vēlāk var izmantot uzlabotas optimizācijas vajadzībām. Šī pieeja arī dod vairāk informācijas, nekā jūs saņemsiet, izmantojot tikai interpretēto kodu profilu skaitītājus.

Diagrammas shēma 1. attēlā attēlo veiktspējas atšķirības starp tīru interpretāciju, klienta, servera un daudzpakāpju kompilāciju. X ass parāda izpildes laiku (laika vienība) un Y ass veiktspēju (ops / laika vienība).

1. attēls. Kompilatoru veiktspējas atšķirības (noklikšķiniet, lai palielinātu)

Salīdzinot ar tīri interpretēto kodu, klienta puses kompilatora izmantošana nodrošina aptuveni 5–10 reizes lielāku izpildes veiktspēju (op / s), tādējādi uzlabojot lietojumprogrammas veiktspēju. Guvuma izmaiņas, protams, ir atkarīgas no tā, cik efektīvs ir kompilators, kādas optimizācijas ir iespējotas vai ieviestas, un (mazākā mērā) no tā, cik labi ir izstrādāta lietojumprogramma attiecībā uz izpildes mērķa platformu. Tomēr pēdējais ir tas, par ko Java izstrādātājam nekad nevajadzētu uztraukties.

Salīdzinot ar klienta puses kompilatoru, servera puses kompilators parasti palielina koda veiktspēju par izmērāmiem 30 procentiem līdz 50 procentiem. Vairumā gadījumu darbības uzlabošana līdzsvaros papildu resursu izmaksas.

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