Programmēšana

Ātri izveidojiet Java: optimizējiet!

Pēc novatoriskā datorzinātnieka Donalda Knuta domām, "priekšlaicīga optimizācija ir visa ļaunuma sakne". Jebkurš raksts par optimizāciju jāsāk, norādot, ka iemeslu parasti ir vairāk optimizēt, nevis optimizēt.

  • Ja kods jau darbojas, tā optimizēšana ir drošs veids, kā ieviest jaunas un, iespējams, smalkas kļūdas

  • Optimizācija parasti padara kodu grūtāk saprotamu un uzturamu

  • Daži no šeit sniegtajiem paņēmieniem palielina ātrumu, samazinot koda paplašināmību

  • Koda optimizēšana vienai platformai to var vēl vairāk pasliktināt citā platformā

  • Daudz laika var pavadīt optimizācijai, ar nelielu veiktspējas pieaugumu, un tā rezultātā var būt neskaidrs kods

  • Ja jūs esat pārmērīgi apsēsts ar koda optimizēšanu, cilvēki jūs sauks par nerdu aiz muguras

Pirms optimizācijas jums rūpīgi jāapsver, vai jums vispār ir jāoptimizē. Java optimizācija var būt grūti sasniedzams mērķis, jo izpildes vides atšķiras. Izmantojot labāku algoritmu, iespējams, tiks panākts lielāks veiktspējas pieaugums nekā jebkādā zema līmeņa optimizācijas apjomā, un tas, visticamāk, nodrošinās uzlabojumus visos izpildes apstākļos. Parasti pirms zema līmeņa optimizēšanas ir jāapsver augsta līmeņa optimizācija.

Tad kāpēc optimizēt?

Ja tā ir tik slikta ideja, kāpēc vispār optimizēt? Nu, ideālā pasaulē jūs to nedarītu. Bet realitāte ir tāda, ka reizēm vislielākā programmas problēma ir tā, ka tai nepieciešami vienkārši pārāk daudz resursu, un šie resursi (atmiņa, procesora cikli, tīkla joslas platums vai to kombinācija) var būt ierobežoti. Kodu fragmenti, kas programmā notiek vairākas reizes, visticamāk, ir jutīgi pēc lieluma, savukārt kods ar daudzām izpildes iterācijām var būt atkarīgs no ātruma.

Izveidojiet Java ātri!

Ātrums vai tā trūkums kā interpretēta valoda ar kompaktu baitkodu visbiežāk parādās Java kā problēma. Mēs galvenokārt aplūkosim, kā panākt, lai Java darbotos ātrāk, nevis lai tas ietilptu mazākā telpā - lai gan mēs norādīsim, kur un kā šīs pieejas ietekmē atmiņu vai tīkla joslas platumu. Galvenā uzmanība tiks pievērsta pamatvalodai, nevis Java API.

Starp citu, viena lieta mēs nebūs Apspriediet šeit vietējo metožu izmantošanu, kas rakstītas C vai montāžā. Lai gan vietējo metožu izmantošana var uzlabot veiktspēju, tas tiek darīts par Java platformas neatkarības cenu. Atlasītām platformām ir iespējams uzrakstīt gan metodes Java versiju, gan vietējās versijas; tas palielina veiktspēju dažās platformās, neatsakoties no iespējas darboties visās platformās. Bet tas ir viss, ko es teikšu par Java aizstāšanu ar C kodu. (Lai iegūtu papildinformāciju par šo tēmu, skatiet Java padomu "Vietējo metožu rakstīšana".) Šajā rakstā galvenā uzmanība tiek pievērsta tam, kā ātri izveidot Java.

90/10, 80/20, būda, būda, pārgājiens!

Parasti 90 procenti no programmas izspiešanas laika tiek tērēti, izpildot 10 procentus koda. (Daži cilvēki izmanto likumu 80 procenti / 20 procenti, bet mana pieredze, rakstot un optimizējot komerciālas spēles vairākās valodās pēdējo 15 gadu laikā, ir parādījusi, ka formula 90 procenti / 10 procenti ir raksturīga programmām, kuras alkst pēc veiktspējas, jo maz uzdevumu mēdz jāveic ļoti bieži.) Pārējo 90 procentu programmas (kur tika iztērēti 10 procenti izpildes laika) optimizēšanai nav ievērojamas ietekmes uz veiktspēju. Ja jūs spētu panākt, ka 90 procenti koda tiek izpildīti divreiz ātrāk, programma būtu tikai par 5 procentiem ātrāka. Tātad pirmais koda optimizācijas uzdevums ir identificēt programmas 10 procentus (bieži vien tas ir mazāks par šo), kas patērē lielāko daļu izpildes laika. Tas ne vienmēr notiek tur, kur jūs sagaidāt.

Vispārīgas optimizācijas metodes

Ir vairākas izplatītas optimizācijas metodes, kas tiek piemērotas neatkarīgi no izmantotās valodas. Daži no šiem paņēmieniem, piemēram, globālā reģistra piešķiršana, ir sarežģītas stratēģijas mašīnu resursu (piemēram, CPU reģistru) piešķiršanai un neattiecas uz Java baitkodiem. Mēs pievērsīsimies metodēm, kas būtībā ietver koda pārstrukturēšanu un līdzvērtīgu darbību aizstāšanu metodē.

Spēka samazināšana

Stiprības samazināšanās notiek, ja operāciju aizstāj ar līdzvērtīgu operāciju, kas tiek izpildīta ātrāk. Visizplatītākais spēka samazināšanas piemērs ir maiņas operatora izmantošana, lai veselus skaitļus reizinātu un dalītu ar jaudu 2. Piemēram, x >> 2 var izmantot vietā x / 4, un x << 1 aizstāj x * 2.

Kopēja sub izteiksmes izslēgšana

Bieži sastopamās apakšteiksmes izslēgšana noņem liekos aprēķinus. Rakstīšanas vietā

dubultā x = d * (lim / max) * sx; dubultā y = d * (lim / max) * sy;

kopējā apakšteikums tiek aprēķināts vienu reizi un tiek izmantots abiem aprēķiniem:

dubultā dziļums = d * (lim / max); dubultā x = dziļums * sx; dubultā y = dziļums * sy;

Kodu kustība

Koda kustība pārvieto kodu, kas veic darbību vai aprēķina izteiksmi, kuras rezultāts nemainās vai ir nemainīgs. Kods tiek pārvietots tā, lai tas tiktu izpildīts tikai tad, kad rezultāts var mainīties, nevis izpildīt katru reizi, kad rezultāts ir nepieciešams. Visbiežāk tas notiek ar cilpām, taču tas var ietvert arī kodu, kas atkārtojas katrā metodes izsaukumā. Šis ir nemainīgas koda kustības piemērs cilpā:

par (int i = 0; i <x. garums; i ++) x [i] * = Math.PI * Math.cos (y); 

kļūst

dubultā picosy = Math.PI * Math.cos (y);par (int i = 0; i <x. garums; i ++) x [i] * = pikozs; 

Ritināšanas cilpas

Cilpiņu atritināšana samazina cilpas vadības koda pieskaitāmās izmaksas, veicot vairāk nekā vienu darbību katru reizi caur cilpu un tādējādi izpildot mazāk atkārtojumu. Strādājot no iepriekšējā piemēra, ja mēs zinām, ka garums x [] vienmēr ir divu reizinājums, mēs varētu pārrakstīt cilpu šādi:

dubultā picosy = Math.PI * Math.cos (y);par (int i = 0; i <x.garums; i + = 2) { x [i] * = pikozs; x [i + 1] * = pikozs; } 

Praksē tādu cilpu atritināšana kā šī - kurās cilpas indeksa vērtība tiek izmantota cilpā un ir jāpalielina atsevišķi - nedod ievērojamu ātruma pieaugumu interpretētajā Java, jo baitkodos trūkst norādījumu, lai efektīvi apvienotu "+1"masīva indeksā.

Visi šajā rakstā sniegtie optimizācijas padomi iemieso vienu vai vairākus no iepriekš uzskaitītajiem vispārīgajiem paņēmieniem.

Kompilatora palaišana darbā

Mūsdienu C un Fortran kompilatori ražo ļoti optimizētu kodu. C ++ kompilatori parasti ražo mazāk efektīvu kodu, taču tie joprojām ir ceļā uz optimāla koda izveidi. Visi šie kompilatori spēcīgas tirgus konkurences ietekmē ir izgājuši cauri daudzām paaudzēm un kļuvuši par smalki izslīpētiem instrumentiem, lai izspiestu katru pēdējo veiktspējas pilienu no parastā koda. Viņi gandrīz noteikti izmanto visus iepriekš aprakstītos vispārīgos optimizācijas paņēmienus. Bet joprojām ir daudz triku, lai kompilatori radītu efektīvu kodu.

javac, JIT un vietējo kodu sastādītāji

Optimizācijas līmenis, kas javac veic, ja koda sastādīšana šajā brīdī ir minimāla. Pēc noklusējuma tas tiek darīts:

  • Pastāvīga locīšana - kompilators atrisina visas pastāvīgās izteiksmes tā, ka i = (10 * 10) apkopo uz i = 100.

  • Zaru locīšana (lielākoties) - nevajadzīga iet uz tiek novērsti bytecodes.

  • Ierobežota mirušā koda izslēgšana - šādiem paziņojumiem kods netiek ražots ja (nepatiesa) i = 1.

Javac nodrošinātajam optimizācijas līmenim, iespējams, ir dramatiski jāuzlabojas, jo valoda nobriest un kompilatoru pārdevēji sāk nopietni konkurēt, pamatojoties uz kodu ģenerēšanu. Java tikko iegūst otrās paaudzes kompilatorus.

Tad ir tieši savlaicīgi (JIT) kompilatori, kas izpildes laikā pārveido Java baitkodus vietējā kodā. Vairāki jau ir pieejami, un, lai gan tie var dramatiski palielināt jūsu programmas izpildes ātrumu, to veiktā optimizācijas pakāpe ir ierobežota, jo optimizācija notiek izpildes laikā. JIT kompilators vairāk rūpējas par koda ātru ģenerēšanu nekā ātrākā koda ģenerēšanu.

Vietējo kodu kompilatoriem, kas kompilē Java tieši uz vietējo kodu, būtu jāpiedāvā vislielākā veiktspēja, taču par platformas neatkarību. Par laimi, daudzus no šeit sniegtajiem trikiem sasniegs nākamie kompilatori, taču pagaidām ir nepieciešams neliels darbs, lai kompilators gūtu maksimālu labumu.

javac piedāvā vienu veiktspējas opciju, kuru varat iespējot: -O opcija likt kompilatoram iekļaut noteiktus metodes izsaukumus:

javac -O MyClass

Metodes izsaukuma iekļaušana metodes kodu ievieto tieši kodā, kas izsauc metodes izsaukumu. Tas novērš metodes izsaukuma pieskaitāmās izmaksas. Nelielai metodei šī pieskaitāmā summa var būt ievērojama tās izpildes laika procentuālā daļa. Ņemiet vērā, ka tikai metodes deklarētas kā par vienu vai otru Privāts, statisksvai galīgais var uzskatīt par iekļaušanu, jo kompilators statiski atrisina tikai šīs metodes. Arī sinhronizēts metodes netiks iekļautas. Kompilators iekļaus tikai nelielas metodes, kas parasti sastāv tikai no vienas vai divām koda rindām.

Diemžēl javac kompilatora 1.0 versijās ir kļūda, kas ģenerēs kodu, kas nevar nodot baitkoda verificētāju, kad -O tiek izmantota opcija. Tas ir noteikts JDK 1.1. (Baitkoda verificētājs pārbauda kodu, pirms tam tiek atļauts palaist, lai pārliecinātos, ka tas nepārkāpj nevienu Java likumu.) Tas iekļaus metodes, kas atsauces klases dalībniekiem nav pieejamas izsaucēja klasei. Piemēram, ja šīs klases tiek apkopotas kopā, izmantojot -O opcija

A klase {privāts statisks int x = 10; public static void getX () {return x; }} B klase {int y = A.getX (); } 

izsaukums uz A.getX () B klasē tiks iekļauts B klasē tā, it kā B būtu rakstīts šādi:

B klase {int y = A.x; } 

Tomēr tas izraisīs baitkodu ģenerēšanu, lai piekļūtu privātajam A.x mainīgajam, kas tiks ģenerēts B kodā. Šis kods darbosies labi, taču, tā kā tas pārkāpj Java piekļuves ierobežojumus, verificētājs to atzīmēs ar IllegalAccessError pirmo reizi izpildot kodu.

Šī kļūda nepadara -O opcija ir bezjēdzīga, taču jums ir jābūt uzmanīgam, kā to izmantot. Ja to izsauc vienā klasē, tas bez riska var iekļaut noteiktus metodes izsaukumus klases ietvaros. Vairākas klases var sastādīt kopā, ja vien nav iespējamu piekļuves ierobežojumu. Daži kodi (piemēram, lietojumprogrammas) netiek pakļauti baitu koda verificētājam. Jūs varat ignorēt kļūdu, ja zināt, ka kods tiks izpildīts tikai bez verificētāja pakļaušanas. Lai iegūtu papildinformāciju, skatiet manu FAQ par javac-O.

Profilētāji

Par laimi, JDK komplektā ir iebūvēts profilētājs, kas palīdz noteikt, kur tiek pavadīts laiks programmā. Tas sekos līdzi pavadītajam laikam katrā rutīnā un ierakstīs informāciju failā java.prof. Lai palaistu profilētāju, izmantojiet -prof opcija, izsaucot Java tulku:

java -prof myClass

Vai lietošanai kopā ar sīklietotni:

java -prof sun.applet.AppletViewer myApplet.html

Profilera izmantošanai ir daži iebildumi. Profilētāja izvadi nav īpaši viegli atšifrēt. Turklāt JDK 1.0.2 tas saīsina metožu nosaukumus līdz 30 rakstzīmēm, tāpēc dažas metodes var nebūt iespējams nošķirt. Diemžēl ar Mac nav iespēju izsaukt profilētāju, tāpēc Mac lietotājiem neveicas. Papildus tam Sun Java dokumentu lapā (sk. Resursus) vairs nav iekļauta -prof opcija). Tomēr, ja jūsu platforma patiešām atbalsta -prof opciju, vai nu Vladimira Bulatova HyperProf, vai Grega Vaita ProfileViewer var izmantot, lai palīdzētu interpretēt rezultātus (skat. Resursus).

Kodu ir iespējams arī "profilēt", kodā ievietojot precīzu laiku:

ilgs sākums = System.currentTimeMillis (); // veiciet operāciju, kas jāiestata šeit ilgu laiku = System.currentTimeMillis () - start;

System.currentTimeMillis () atgriež laiku 1/1000 sekundes sekundēs. Tomēr dažām sistēmām, piemēram, Windows personālajam datoram, ir sistēmas taimeris ar mazāku (daudz mazāku) izšķirtspēju nekā 1/1000 sekundes daļa. Pat 1/1000 sekundes daļa nav pietiekami ilga, lai precīzi noregulētu daudzas darbības. Šādos gadījumos vai sistēmās ar zemas izšķirtspējas taimeriem var būt nepieciešams noteikt laiku, cik ilgi operācija jāatkārto n reizes un pēc tam daliet kopējo laiku ar n lai iegūtu faktisko laiku. Pat tad, ja ir pieejama profilēšana, šī metode var būt noderīga, lai noteiktu noteiktu uzdevumu vai darbību.

Šeit ir dažas nobeiguma piezīmes par profilēšanu:

  • Vienmēr ievadiet kodu pirms un pēc izmaiņu veikšanas, lai pārliecinātos, ka izmaiņas vismaz testa platformā ir uzlabojušas programmu

  • Mēģiniet veikt katru laika pārbaudi vienādos apstākļos

  • Ja iespējams, izdomājiet testu, kas nav atkarīgs no lietotāja ieejas, jo lietotāja reakcijas variācijas var izraisīt rezultātu svārstības

Benchmark sīklietotne

Benchmark sīklietotne mēra laiku, kas nepieciešams operācijas veikšanai tūkstošiem (vai pat miljonu) reižu, atņem laiku, kas pavadīts citu darbību veikšanai, izņemot testu (piemēram, cilpas pieskaitāmās izmaksas), un pēc tam izmanto šo informāciju, lai aprēķinātu, cik ilgi katra darbība jāveic paņēma. Katru testu tā veic aptuveni vienu sekundi. Mēģinot novērst nejaušu aizkavēšanos no citām darbībām, kuras dators var veikt pārbaudes laikā, tas katru testu veic trīs reizes un izmanto labāko rezultātu. Tas arī mēģina novērst atkritumu savākšanu kā faktoru testos. Tāpēc, jo vairāk atmiņas ir pieejams etalonam, jo ​​precīzāki ir etalona rezultāti.

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