Professional Documents
Culture Documents
Softverski Kod
Softverski Kod
TEHNOLOGIJE
Projekat
Nastavnik:
Goran Radi
Student:
Cveti Branko 65/06
Datum predaje:
22.08.2011.
SADRAJ
SADRAJ ............................................................................................................................................... 2
REZIME .................................................................................................................................................. 4
UVOD ...................................................................................................................................................... 5
STRATEGIJE ZA UNAPREENJE SOFTVERSKOG KODA ................................................... 6
PERFORMANSE................................................................................................................................... 6
KVALITATIVNE KARAKTERISTIKE I PERFORMANSE ............................................................... 6
PERFORMANSE I OPTIMIZACIJA KODA...................................................................................... 6
ZAHTEVI SOFTVERA........................................................................................................................ 7
DIZAJN SOFTVERA .......................................................................................................................... 7
DIZAJN KLASA I RUTINA................................................................................................................ 8
INTERAKCIJE SA OPERATIVNIM SISTEMOM ............................................................................ 8
KOMPILACIJA KODA ........................................................................................................................ 8
HARDVER ........................................................................................................................................... 8
OPTIMIZACIJA KODA ...................................................................................................................... 9
UVOD U OPTIMIZACIJU KODA (CODE TUNING)................................................................ 9
PARETOV PRINCIP .........................................................................................................................10
BAPSKE PRIE ................................................................................................................................10
KADA OPTIMIZOVATI....................................................................................................................13
OPTIMIZACIJE KOMPLAJLERA ....................................................................................................14
VRSTE ZAGUENJA ........................................................................................................................14
ESTI IZVORI NEEFIKASNOSTI.................................................................................................15
RELATIVNA CENA U PERFORMANSAMA ESTO KORIENIH OPERACIJA .....................17
MERENJA.............................................................................................................................................19
MERENJA TREBA DA BUDU PRECIZNA .....................................................................................20
ITERACIJE ..........................................................................................................................................21
PRISTUP OPTIMIZACIJI KODA ................................................................................................22
TEHNIKE ZA OPTIMIZACIJU KODA ........................................................................................23
LOGIKA ................................................................................................................................................23
PREKINITE SA ISPITIVANJEM USLOVA KADA SAZNATE KONANU VREDNOST...........23
POREAJTE USLOVE PO UESTALOSTI ISPUNJAVANJA .....................................................25
POREDITE PERFORMANSE SLINIH LOGIKIH STRUKTURA.............................................27
ZAMENITE KOMPLIKOVANE IZRAZE TABELAMA (TABLE LOOKUPS) ...............................27
KORISTITE LENJU PROCENU (LAZY EVALUATION) ...........................................................28
PETLJE..................................................................................................................................................29
RAZBIJANJE CIKLUSA (UNSWITCHING)..................................................................................29
SPAJANJE PETLJI (LOOP JAMMING ILITI LOOP FUSION) ....................................................30
RAZVIJANJE PETLJE (UNROLLING) ...........................................................................................31
MINIMIZIRAJTE POSAO KOJI SE OBAVLJA UNUTAR PETLJI...............................................32
MARKERSKE VREDNOSTI (SENTINEL VALUES).....................................................................33
POSTAVLJANJE NAJEE KORIENE PETLJE UNUTRA ...................................................34
SMANJENJE SNAGE (STRENGTH REDUCTION)......................................................................35
TRANSFORMACIJE PODATAKA.................................................................................................35
KORISTITE CELE BROJEVE UMESTO BROJEVA SA POKRETNIM ZAREZOM....................36
KORISTITE NAJMANJI MOGUI BROJ DIMENZIJA NIZA .....................................................36
MINIMIZIRAJTE REFERENCIRANJE NIZA.................................................................................37
KORISTITE DODATNE INDEKSE.................................................................................................37
KORISTITE KEIRANJE (CACHING) ..........................................................................................38
IZRAZI .................................................................................................................................................40
ISKORISTITE ALGEBARSKE IDENTITETE ................................................................................40
KORISTITE REDUKCIJU SNAGE (STRENGTH REDUCTION)................................................40
INICIJALIZUJTE U VREME KOMPAJLIRANJA ...........................................................................41
UVAJTE SE SISTEMSKIH POZIVA (RUTINA).........................................................................42
KORISTITE PRAVILAN TIP KONSTANTI....................................................................................43
PRERAUNAJTE (PRECOMPUTE) REZULTATE.........................................................................43
2/50
3/50
REZIME
Performanse su samo jedan aspekt ukupnog kvaliteta softvera i obino nisu ni
najvaniji. Dalje, dobro optimizovani kod je samo jedan aspekt ukupnih performansi i skoro po
pravilu nije i najznaajniji. Programska arhitektura, detaljni dizajn, upotrebljene strukture
podataka i izbor algoritama u najveem broju sluajeva imaju vei uticaj na performanse
programa (vreme izvravanja i zauzee memorije) nego to to ima efikasnost njegovog
programskog koda.
Kvantitativna merenja su kljuni deo maksimizirananja performansi. Ona su
neophodna da bi se pronali delovi programa ija bi optimizacija performansi zaista dala
rezultate. Takoe, merenja su ponovo potrebna kada treba verifikovati da su uraene
optimizacije poboljale (i to u dovoljnoj znaajnoj meri) umesto da degradiraju performanse
softvera. Veina aplikacija troi veliki deo vremena izvravanja (execution time) u malim
delovima koda. Ne moete znati koji su to delovi dok ne izvrite merenja. Kada pronaete te
delove, tzv. uska grla (hotspots), obino je potrebno vie iteracija u optimizaciji kako bi se
postigla eljena poboljanja u performansama. Najbolji nain da se pripremite za rad na
performansama putem optimizacije koda (code tuning) tokom inicijalnog programiranja jeste
da piete ist, standardni kod koji je lak za razumevanje i modifikaciju.
4/50
UVOD
Mnogi softverski inenjeri preporuuju ono to neki nazivaju "pristup odugovlaenja"
optimizaciji softverskog koda. Taj pristup kae da optimizaciju treba odugovlaiti to je
mogue vie, a ako je ikako mogue da optimizaciju u potpunosti zaobiete onda to i uinite.
Prerana ili preesta optimizacija prosto nije dobar pristup inenjerstvu. Svakako je bolje da
imate program koji radi, nego da imate brz program koji pada ili daje netane informacije. Sa
druge strane, teko da ete uspeti da napiete uspenu aplikaciju u ovom trenutku bez
bavljenja optimizacijom na nekom nivou u procesu stvaranja aplikacije. Kvalitetan kompajler
moe da vam pomogne, ali samo donekle. Istina je ipak, da vi kao programer daleko bolje
razumete svoju aplikaciju (njenu primenu, ulazne vrednosti, uslove u kojima e raditi...) nego
kompajler koji jednostavno nema takve informacije. Kao to kae Michael Abrash, najbolji
kompajler je onaj "izmeu tvojih uiju".
Postoji mnogo nivoa na kojima se moe vriti optimizacija performansi vaih
aplikacija. Neke od tehnika koje emo obraditi bolje rade na kompajliranim jezicima, neke
bolje na interpretiranim. Sutina jeste u tome da kakve god optimizacije da primenite, vano
je da izmerite efekat predvienih optimizacija i da to inite redovno kako bi bili sigurni koja je
optimizacija i kakve dala efekte.
5/50
PERFORMANSE
Optimizacija koda (code tuning) je samo jedna od metoda kojima se moe postii
poboljanje performansi. Veoma esto moete nai druge naine da poboljate performanse
vie (i za manje vremena), a pri tome inei manju tetu kodu, nego to biste to uinili
optimizacijom koda. Ovde emo razmotriti neke od opcija.
6/50
odluite da poboljate brzinu ili veliinu na nivou programskog koda. Razmislite o efikasnosti iz
svake od sledeih taki gledita:
Zahtevi softvera
Dizajn softvera
Dizajn klasa i rutina (funkcija)
Interakcije sa operativnim sistemom
Kompilacija koda
Hardver
Optimizacija koda (code tuning)
ZAHTEVI SOFTVERA
Performanse se navode kao korisniki zahtev daleko ee nego to to zaista jesu.
Barry Boehm prepriava priu iz TRW inc. o sistemu koji je inicijalno imao zahtev za reakcijom
ispod jedne sekunde. Ovakav zahtev je mogao biti reen veoma kompleksnim dizajnom ija bi
realizacija kotala dodatnih 100 miliona dolara. Dalje analize su utvrdile da bi korisnici bili
zadovoljni i reakcijom sistema u prve 4 sekunde 90 posto vremena. Promena zahteva za
reakcijom sistema smanjila je ukupne trokove za oko 70 miliona dolara. Poruka glasi: Pre
nego to investirate vreme u reavanje problema sa performansama, budite sigurni da
reavate problem koji treba da bude reen.
DIZAJN SOFTVERA
Dizajn softvera ukljuuje glavne pravce dizajna za pojedinani program, a to je
uglavnom nain na koji je program podeljen u manje celine, odnosno klase. Odreeni dizajni
softvera oteavaju pisanje koda za sisteme visokih performansi. Drugi, ine da pisanje koda za
visoko-performansne sisteme bude lako i prirodno.
Pogledajmo primer iz realnog radnog okruenja programa koji se bavi sakupljanjem
podataka za koji je protok merenja podataka identifikovan kao kljuna osobina. Svako merenje
ukljuuje vreme potrebno za elektronsko merenje, kalibrisanje vrednosti, skaliranje vrednosti i
konverziju sa senzorskih jedinica podataka (kao to su milivolti) u inenjerske jedinice (kao to
su recimo stepeni Celzijusovi).
U ovom sluaju, bez uzimanja u obzir rizika koji sa sobom nosi dizajn visokog nivoa,
programeri bi se nali u situaciji da pokuavaju da optimizuju jednaine za reavanje polinoma
13-og reda u softveru, odnosno polinoma sa 14 promenljivih ukljuujui i promenljive 13-og
stepena. Umesto toga, oni su reili problem sa drugaijim hardverom i dizajnom visokog nivoa
koji koristi desetine polinoma 3. reda. Ovakva promena nije mogla da bude sprovedena putem
prilagoavanja koda, a takoe postoji verovatnoa da bilo koja koliina prilagoavanja koda ne
bi mogla da rei problem. Ovo je samo jedan od primera problema koji je morao biti reen na
nivou dizajna softvera.
Ako znate da su veliina i brzina programa vani, dizajnirajte arhitekturu programa na
takav nain da uz razumne programerske napore moete da postignete ciljeve u pogledu
veliine i brzine. Dizajnirajte arhitekturu koja je orjentisana na performance i onda postavite
ciljeve u pogledu resursa za svaki pojedinani podsistem, osobinu ili klasu. Ovo e vam pomoi
na vie naina:
7/50
zahteve u pogledu resursa, onda e i itav system ispuniti zahtev u pogledu resursa. Na
ovaj nain, moete da identifikujete podsisteme koji imaju problema sa ispunjavanjem
sopstvenih ciljeva prilino rano i da ih ciljate u smislu potencijalnog redizajna ili
prilagoavanja koda.
Sama injenica da ste postavili eksplicitne ciljeve poveava ansu da e oni biti
postignuti. Programeri rade ka ispunjenju ciljeva kada znaju ta su ciljevi; to su
eksplicitnije i preciznije objanjeni, programerima je lake da rade.
KOMPILACIJA KODA
Dobri kompajleri pretvaraju programski kod jezika visokog nivoa u dobro optimizovani
mainski kod. Ako izaberete odgovarajui kompajler, neete morati da brinete o
optimizacijama na niovu koda, ak ta vie mogue je da e sitno doterivanje koda sa ciljem
da se poboljaju perfomanse samo usporiti program jer ete na taj nain samo zbuniti
kompajler.
HARDVER
Ponekad je najjeftiniji, najistiji i najbolji nain da poboljate performanse programa
kupovina novog hardvera. Ako recimo distribuirate aplikaciju za iroku upotrebu stotina ili
hiljada korisnika kupovina novog hardvera nije realistina opcija. Ali, ako razvijate softver po
narudbini za nekoliko korisnika u jednoj kompaniji, nadogradnja hardvera je moda
najjeftinija opcija. Nadogradnja hardvera tedi poetno ulaganje u merenje performansi, tedi
8/50
budue trokove odravanja, a takoe i poboljava performance svih aplikacija koje rade na
tom hardveru.
OPTIMIZACIJA KODA
Optimizacija koda je praksa odnosno posao izmene ispravnog koda na takav nain da
se on izvrava efikasnije. Optimizacija se odnosi na izmene manjeg obima koje utiu na
pojedinanu klasu, rutinu ili najee nekoliko linija programskog koda. Optimizacija se ne
odnosi na krupnije promene u dizajnu koda ili druge vidove poboljanja performansi visokog
nivoa.
Vi moete da napravite dramatina poboljanja na svakom od nivoa poevi od dizajna
softvera pa do prilagoavanja koda. Jon Bentey navodi argument da je u nekim sluajevima
poboljanja na svakom od nivoa mogue pomnoiti. Kako je mogue da ostvarite poboljanje
od 10 puta na svakom od nivoa, to implicira da se potencijalno moe napraviti poboljanje od
ak million puta po pitanju efikasnosti. Iako takvo umnoavanje potenijala za poboljanje
performansi zahteva aplikaciju u kojoj su dobici na performansama nezavisni na svakom nivou,
to je veoma retko, takav potencijal ipak pedstavlja veliki motiv za dalji rad.
9/50
PARETOV PRINCIP
Paretov princip, takoe poznat i kao pravilo 80/20, tvrdi da moete da postignete 80
procenata rezultata sa 20 procenata napora. Ovaj princip se moe primeniti u mnogim
oblastima sem programiranja, ali definitivno vai kada govorimo o optimizaciji programa.
Barry Boehm navodi da 20 procenata rutina u programu troi 80 procenata vremena
izvravanja (execution time) istog programa (1987b). U svom klasinom radu Empirijska
studija Fortran programa, Donald Knuth je utvrdio da je manje od 4 procenta koda u
programu odgovorno za 50 procenata vremena njegovog izvravanja (1971).
Knuth je koristio profajler za brojanje linija koda (line-count profiler) da bi otkrio ovaj
iznenaujui odnos, a implikacije na optimizaciju su jasne. Prilikom optimizacije trebalo bi da
merite kod kako bi nali najkorienije delove (hot spots) i onda da svoje resurse uloite u
optimizaciju tih nekoliko procenata koda koji se najvie koriste. Knuth je takoe profilisao i
sopstveni program za brojanje linija i pronaao da on troi polovinu vremena izvravanja u
samo 2 petlje. Nakon toga je promenio samo par linija koda u okviru pomenutih petlji i
duplirao brzinu sopstvene aplikacije za nepunih sat vremena.
Jon Bentley opisuje sluaj u kojem je program od 1000 linija koda troio 80 procenata
vremena izvravanja u rutini od samo 5 linija za raunanje kvadratnog korena. Prepravkom te
rutine ubrzao je samu rutinu trostruko, a kompletnu aplikaciju 2 puta (1988). Paretov princip
je posluio i kao izvor za generalni savet da piete najvei deo koda u interpretiranom jeziku
kao to je Python i da nakon toga prepiete najkorienije delove (hot spots) u nekom brzom
kompajliranom jeziku kao to je C. Bentley takoe navodi sluaj u kojem je tim programera
pronaao da operativni system polovinu svog izvrnog vremena provodi u maloj
neoptimizovanoj petlji. Ponovo su napisali pomenutu petlju i uinili je 10 puta brom.
Meutim, nije dolo do bilo kakve promene u brzini izvraavanja aplikacija. Kada su malo bolje
pogledali shvatili su da su optimizovali sistemsku idle petlju (idle loop).
Tim koji je kreirao programski jezik ALGOL - deku veine modernih jezika i jedan od
najuticajnijih jezika ikada dao je savet programerima: Najbolje je neprijatelj dobrog. Rad ka
savrenstvu moe da ili veoma esto spreava da se zadatak uopte i zavri. Prvo zavri, pa
onda usavravaj. Deo koji treba da bude savren obino je veoma mali., njihova je poruka.
BAPSKE PRIE
Veliki deo onoga to ste uli o optimizaciji koda je netaan, ukljuujui i este zablude:
Smanjanje broja linija koda u jezicima visokog nivoa poboljava brzinu ili
smanjuje veliinu rezultujueg mainskog koda neistina. Mnogi programeri se uporno
dre verovanja da ako napiu kod koji ima samo jednu ili par linija, da e takav kod biti
najefikasniji mogui. Pogledajmo sledei kod koji inicijalizuje niz od 10 elemenata:
for i = 1 to 10
a[ i ] = i
end for
Da li bi ste rekli da je ovakav kod bri ili sporiji od naredne verzije koda koja radi isti
posao?
10/50
1 ] = 1
2 ] = 2
3 ] = 3
4 ] = 4
5 ] = 5
6 ] = 6
7 ] = 7
8 ] = 8
9 ] = 9
10 ] = 10
Ako biste primenili gorenavedenu dogmu da je krai kod i bri, pomislili bi ste da je
gornji kod bri. Meutim, testovi u Microsoft Visual Basic-u i Java-i pokazuju da je drugi deo
koda bri za barem 60 procenata od prvog. U narednoj tabeli moete pogledati brojke:
Jezik
Visual Basic
Java
For petlja
Runa inicijalizacija
Uteda
Odnos performansi
8.47
3.16
63%
2.5:1
12.6
3.23
74%
4:1
Tabela 1. Inicijalizacija vrednosti niza od 10 elemenata
Ovo svakako ne znai da poveanje broja linija koda jezika visokog nivoa uvek
poboljava brzinu ili smanjuje veliinu mainskog koda. Ali, ovo govori da bez obzira na
estetsku lepotu pisanja neega to ima manje linija, ne postoji nikakav prdvidiv odnos
izmeu broja linija koda u jezicima visokog nivoa i konane veliine i brzine ukupnog
programa.
Odreene operacije su verovatno bre ili manje od drugih neistina. Ne
postoji nimalo prostora za odrednice kao to je verovatno kada priamo o performansama.
Morate uvek da merite performance da bi ste znali da li je nainjena promena dala rezultate ili
nije. Pravila igre se menjaju svaki put kada promenite programski jezik, kompajler, verziju
kompajlera, biblioteke ili njihove verzije, procesore, koliinu raspoloive radne memorije itd
Ova pojava ukazuje na to da postoji nekoliko razloga da se performance ne
poboljavaju putem prilagoavanja koda. Ako elite da program bude portabilan, morate imati
na umu da tehnike koje poboljavaju performance u jednom okruenju mogu da ih degradiraju
u drugom. Ako recimo promenite kompajler ili ga nadogradite na noviju verziju, novi kompajler
e moda automatski optimizovati kod na isti nain na koji bi ste vi runo optimizovali kod i
va rad bi bio uzaludan. Ili jo gore, runa optimizacija koda moe da pobedi optimizacije
kompajlera koje su dizajnirane da rade sa standardnim kodom i da uspori vau aplikaciju.
Kada optimizujete kod, vi praktino implicitno ponovno profiliete svaku optimizaciju
svaki put kada promenite proizvoaa kompajlera, njegovu verziju, verziju biblioteke itd... Ako
ne uradite ponovno profilisanje, optimizacija koja poboljava performanse u jednoj verziji
kompajlera mogla bi da degradira performanse kada god promenite okruenje za kreiranje
mainskog koda. Ne treba se baviti malim efikasnostima: prerana optimizacija je koren sveg
zla Donald Knuth.
Kod treba optimizovati tokom rada neistina. Jedna teorija kae da ako teite
pisanju najbreg i najmanjeg mogueg koda, trebalo bi da optimizujete kod tokom pisanja
svake rutine, a va program e biti brz i mali. Ovaj pristup kreira situaciju da programer ne
vidi umu od drvea u kojoj se ignoriu znaajne globalne optimizacije jer je programer
prezauzet mikro-optimizacijama. Slede glavni problemi sa optimizovanjem koda tokom rada:
11/50
Skoro da je nemogue identifikovati uska grla pre nego to konaan program proradi.
Programeri su veoma loi u proceni koja 4 procenta koda su vana za 50 procenata
vremena izvravanja poto jednostavno nemaju uvid u budue navike korisnika.
Programeri koji optimizuju tokom normalnog programiranja, u proseku, provedu 96
procenata vremena na optimizaciju koda koji uopte i nije potrebno optimizovati. To
ostavlja jako malo vremena za optimizaciju onih najvanijih 4 procenta koja se
stvarno raunaju.
U onim retkim situacijama kada programeri identifikuju uska grla korektno, oni se
fokusiraju na ta uska grla pruajui im preveliku panju. Na taj nain dozvole drugim
uskim grlima da postanu kritina. Ponovo, konani efekat je pogoranje performansi.
Optimizacije koje se rade nakon to je program kompletiran mogu da identifikuju oblast
u kojem se problem javlja i njegovu relativnu vanost tako da je mogue efikasno
rasporediti vreme koje je namenjeno za optimizaciju koda.
Fokusiranje na optimizaciju tokom inicijalnog razvoja programa odvlai panju sa
postizanja drugih programskih ciljeva. Programeri zarone u analizu algoritama i
volebne debate koje na kraju ne donese vrednost za korisnike programa. Drugi ciljevi
kao to su ispravnost koda, sakrivanje informacija (uaurivanje) i itljivost koda
postanu sekundarni ciljevi, iako je performanse lake kasnije poboljati nego druge
navedene ciljeve. Naknadni rad na poboljanju performansi obino utie na manje od 5
procenata programskog koda. Da li biste se radije vratili i radili na optimizaciji
performansi 5 procenata koda ili na itljivosti na 100 procenata koda?
12/50
ispunio zahteve korisnika. Meutim, on je ipak odleteo ponovo za Detroit i ubedio rukovodioce
da je mogue zavriti projekat.
Nakon toga je trebalo samo da ubedi prvobitne programere koji su radili na
projektu. Oni su odsluali paljivo njegovu prezentaciju i kada je zavrio, jedan od kreatora
starog sistema je upitao: A koliko je potrebno da se va program izvri? To varira, ali oko
deset sekundi po unosu, rekao je novi programer. Aha, ali naem programu je potrebna
samo jedna sekunda po unosu, ree veteran lagano se zavalivi unazad koji je ve bio
zadovoljan to je poentirao na samom startu razgovora, dok su se ostali programeri sloili.
Meutim, novi programer nije bio zastraen njihovom konstatacijom: Da, ali va program ne
radi! Ako i moj program ne mora da radi, ja mogu da ga napravim da se izvrava skoro
trenutno.
Za odreenu klasu projekata, brzina ili veliina predstavljaju osnovne ciljeve. Ovakvi
projekti su prilino retki i dosta su rei nego to veina ljudi misli. Za takve projekte, rizici koji
se tiu performansi se moraju ukalkulisati u osnovni dizajn. Za ostale projekte, rana
optimizacija predstavlja znaajnu pretnju za ukupan kvalitet softvera, ukljuujui i
performanse.
KADA OPTIMIZOVATI
Koristite dizajn visokog kvaliteta. Napravite da program radi tano. Napravite da bude
modularan i lako izmenljiv tako da moete da radite lako sa njim kasnije. Kada je program
zavren i taan, proverite performanse. Ako primetite da je program spor ili glomazan, uinite
ga efikasnim. Ali, nemojte da optimizujete stvari sve dok niste sigurni da to i treba da uradite.
Jackson-ovo pravilo optimizacije:
Pre nekoliko godina autor knjige je radio na C++ projektu koji je kreirao grafiki izlaz
za analizu investicionih podataka. Nakon to je njegov tim zavrio prvi graf, uraena su
testiranja koja su pokazala da je potrebno oko 45 minuta za iscrtavanje pojedinanog grafa. To
oigledno nije bilo prihvatljivo. Tim je odrao sastanak da bi se videlo ta dalje raditi. Jedan od
programera je, iritiran situacijom, uzviknuo: Ako elimo da imamo bilo kakvu ansu da
pustimo prihvatljiv proizvod, moramo da krenemo sa ponovnim pisanjem celog baznog koda u
asembleru odmah!. Autor knjige je odgovorio da ne misli tako da je vrlo verovatno da je 4
procenta koda odgovorno za 50 ili vie procenata uskih grla koje utiu na performanse. Bilo bi
dobro da se potrae ta 4 procenta koda kako se projekat blii kraju, nastavio je. Posle jo
malo vikanja, menader projekta je dodelio autoru knjige da uradi inicijalno testiranje
performansi.
Kao to je to esto sluaj, jedan dan testiranja je identifikovao nekoliko ljateih
uskih grla u kodu. Samo par optimizacija koda je smanjilo vreme iscrtavanja grafova sa 45
minuta na manje od 30 sekundi. Daleko manje od jednog procenta koda je bilo odgovorno za
90 procenata vremena izvravanja. Do trenutka kada je proizvod puten na trite nekoliko
meseci kasnije, par dodatnih optimizacija koda je skratilo vreme iscrtavanja na neto vie od
jedne sekunde.
13/50
OPTIMIZACIJE KOMPLAJLERA
Optimizacije modernih kompajlera su moda monije nego to biste oekivali. U
situaciji sa grafovima koja je opisana, kompajler je odradio bolji posao sa ugnjedenim
petljama, nego to je autor bio u stanju da napie na navodno efikasniji nain. Kada
kupujete kompajler, uporedite performanse svakog kompajlera na vaoj aplikaciji. Svaki
kompajler ima razliite prednosti i slabosti i neke od njih e vie odgovarati vaoj aplikaciji ili
tipu aplikacija koje izraujete.
Optimizacije kompajlera su efikasnije kada se radi sa standardnijim kodom nego kada
kompajler radi sa optimizovanim, lukavim kodom. Ako ste u kodu radili sa pametnim
stvarima kao to je igranje sa indeksima petlji, vaem kompajleru e biti tee da uradi svoj
posao, a va program e zbog toga trpeti.
Sa dobrim optimizacijama kompajlera, va kod moe imati ubrzanje od itavih 40
procenata ili vie. Mnoge od tehnika za optimizaciju koda, koje emo pogledati kasnije, mogu
da proizvedu maksimalan dobitak od 15 do 30 procenata. Zato onda ne pisati ist, standardan
kod i pustiti kompajler da obavi svoj deo posla? U sledeoj tabeli se moe videti rezultat
nekoliko testova iz kojih se moe zakljuiti koliko je kompajler ubrzao rutinu za insertion-sort:
jedina razlika izmeu verzija rutina jeste da su one kompajlirane sa iskljuenim optimizacijama
za prvo kompajliranje i ukljuenim za drugo kompajliranje. Oigledno, neki kompajleri bolje
optimizuju nego drugi, dok neki bolje rade bez optimizacije. Neke Javine virtualne maine
(JVM) su oigledno bolje nego druge. Naravno, da bi dobili najbolje rezutate za vae aplikacije,
morate da proverite kompajler, JVM ili oba da bi izmerili efekte.
Vreme bez
Vreme sa
Uteda u
Odnos
optimizacija
optimizacijama
vremenu
performansi
kompajlera
kompajlera
C++ kompajler 1
2.21
1.05
52%
2:1
C++ kompajler 2
2.78
1.15
59%
2.5:1
C++ kompajler 3
2.43
1.25
49%
2:1
C# kompajler
1.55
1.55
0%
1:1
Visual Basic
1.78
1.78
0%
1:1
Java VM 1
2.77
2.77
0%
1:1
Java VM 2
1.39
1.38
<1%
1:1
Java VM 3
2.63
2.63
0%
1:1
Tabela 2. Brzina izvravanja koda kompajliranog sa optimizacijama i bez optimizacija kompajlera
Jezik
VRSTE ZAGUENJA
Kada radite optimizaciju koda nalaziete na delove programa koji su spori kao pu ili
veliki kao Godzila i menjati ih tako da budu brzi kao munje i toliko mali (tanki) tako da se
mogu sakriti u pukotine izmeu bajtva u RAM-u. Uvek morate da profiliete program da biste
znali, sa iole sigurnosti, koji delovi su spori i glomazni, ali i pored toga postoje odreene
operacije i strukture koje imaju dugu istoriju lenjosti i gojaznosti. Za poetak, nije loa ideja
da vaa analiza startuje prouavanjem ba ovih operacija.
14/50
Uteda u
Odnos
vremenu
performansi
6.04
0.000
~100%
N/A
12.8
0.010
~100%
1000:1
Tabela 3. Vremena pristupa nasuminim elementima, niz od 100 elemenata
Vreme za fajl
Vreme za RAM
Prema izmerenim podacima, pristup elementima niza u memoriji je reda veliina 1000
puta bri od pristupa podacima u eksternom fajlu. ak ta vie, sa C++ kompajlerom koji je
korien na testu, neophodno vreme koje je bilo potrebno za pristup podacima u memoriji je
tako kratko da ga nije bilo mogue izmeriti. Poreenje performansi na slinom testu, ali sa
sekvencijalnom pristupom elementima niza izleda ovako:
Uteda u
Odnos
vremenu
performansi
C++
3.29
0.021
~99%
150:1
C#
2.60
0.030
~99%
85:1
Tabela 4. Vremena sekvencijalnog pristupa elementima niza, testovi izvreni nad 13 puta veim podacima
Jezik
Vreme za fajl
Vreme za RAM
Da je test koristio sporiji medijum za eksterni pristup, na primer tvrdi disk preko
mrene konekcije razlika bi bila jo znaajnija. Test performansi u situaciji kada koristite
slian (sluajni) pristup elementima na lokaciji na mrei umesto na lokalnoj maini izleda
ovako:
Jezik
Vreme za fajl
Vreme za RAM
Uteda u vremenu
C++
6.04
6.64
-10%
C#
12.8
14.1
-10%
Tabela 5. Vremena sekvencijalnog pristupa elementima niza na mrenoj lokaciji
Naravno, ovi rezultati mogu da variraju drastino u zavisnosti od brzine vae mree,
optereenja, udaljenosti lokalne maine od tvrdog diska kome se pristupa, brzine mrenog
diska u odnosu na lokalni disk, trenutne faze meseevih mena i drugih faktora Sveukupno
gledano, efekat pristupa podacima u radnoj ili jo bolje ke (cache) memoriji je dovoljno
snaan da u svakoj situaciji razmislite o I/O operacijama u kritinim delovima programa kod
kojih se zahteva brzina, odnosno efikasnost.
Stranienje (paging): Operacija koja zahteva od operativnog sistema da menja
stranice radne memorije je daleko sporija od operacije koja radi samo sa jednom stranicom
memorije. Ponekad sitna promena ima za posledicu velike razlike. U sledeem primeru,
programer je napisao petlju za inicijalizaciju koja je proizvela veliki broj pogrenih memorijskih
stranica (page faults), na sistemu koji koristi stranice veliine 4KB. Sledi primer uraen u Java
programskom jeziku koji proizvodi veliki broj loih stranica:
15/50
Napiite sopstvene servise. Ponekad vam je potreban samo mali deo funkcionalnosti
koje vam daje sistmska rutina: Vi moete da kreirate sopstveni servis koji bi zadovoljio
iskljuivo vae potrebe, bez obimnijeg korienja resursa. Pisanjem sopstvenog servisa
dobijate neto to je bre, manje i bolje prilagoeno vaim potrebama.
Izbegavajte odlazak u sistem.
Saraujte sa proizvoaem operativnog sistema kako bi on napravio poziv koji
je efikasniji. Veina proizvoaa operativnih sistema eli da unapredi sopstvene
proizvode i drago im je da uju za delove sistema koji imaju slabe performance. Moe
da se desi da u poetku budu malo dangrizavi, ali u sutini oni su veoma
zainteresovani za takve stvari.
16/50
17/50
Operacija
Osnovno (celobrojno dodeljivanje)
Pozivi rutina
Poziv rutine koja nema parametre
Poziv privatne rutine koja nema
parametre
Poziv privatne rutine koja ima 1
parametar
Poziv privatne rutine koja ima 2
parametra
Poziv rutine objekta
Poziv izvedene rutine
Poziv polimorfne rutine
Referenciranje objekata
Dereferenciranje objekata 1. nivoa
Dereferenciranje objekata 2. nivoa
Svako dodatno dereferenciranje
Operacije sa celim brojevima
Dodeljivanje celobrojne vrednosti
(lokalno)
Dodeljivanje celobrojne vrednosti
(nasleeno)
Dodavanje celih brojeva
Oduzimanje celih brojeva
Mnoenje celih brojeva
Celobrojno deljenje
Operacije brojevima sa pokretnim
zarezom
Dodeljivanje brojeva sa pokretnim
zarezom
Sabiranje brojeva sa pokretnim zarezom
Oduzimanje brojeva sa pokretnim
zarezom
Mnoenje brojeva sa pokretnim zarezom
Deljenje brojeva sa pokretnim zarezom
Transcedentne funkcije
Kvadratni koren brojeva sa pokretnim
zarezom
Sinus broja sa pokretnim zarezom
Logaritam broja sa pokretnim zarezom
Eksponencijalna funkcija broja sa
pokretnim zarezom ey
Nizovi
Pristup nizu celobrojnih vrednosti sa
konstantnim indeksom
Pristup nizu celobrojnih vrednosti sa
promenljivim indeksom
Pristup dvo-dimenzionalnom nizu
celobrojnih vrednosti sa konstantnim
indeksima
Pristup dvo-dimenzionalnom nizu
celobrojnih vrednosti sa konstantnim
indeksima
Pristup nizu brojeva sa pokretnim
Primer
i=j
C++
1
Java
1
foo()
this.foo()
1
1
n/a
0.5
this.foo(i)
1.5
0.5
this.foo(i, j)
0.5
bar.foo()
derivedBar.foo()
abstractBar.foo()
2
2
2.5
1
1
2
i = obj.num
i = obj1.obj2. num
i = obj1.obj2.obj3
1
1
Nije merljivo
1
1
Nije
merljivo
i=j
i=j
i=j+k
i=j-k
i=j*k
i=j\k
1
1
1
5
1
1
1
1.5
x=y
x=y+z
x=y-z
1
1
1
1
x=y*z
x=y/z
1
4
1
1
x = sqrt( y )
15
x = sin( y )
x = log( y )
x = exp( y )
25
25
50
20
20
20
i = a[ 5 ]
i = a[ j ]
x = z[ 3, 5 ]
i = a[ j, k ]
x = z[ 5 ]
18/50
MERENJA
Usled injenice da mali delovi programa zauzimaju disproporcionlano veliki deo
vremena za izvravanje, uvek treba meriti kod kako bi se pronala uska grla. Jednom, kada
ste pronali uska grla i optimizovali ih, testirajte kod ponovo da bi ste pravilno procenili koliko
ste ga unapredili. Mnogi aspekti performansi su suprotni intuitivnom miljenju. Primer koji smo
neto ranije pokazali, gde je 10 linija koda znaajno bre od jedne linije koda istog
semantikog znaenja, je samo jedan primer kako vas kod moe iznenaditi. Takoe, ni ranije
iskustvo u optimizacijama nije garant da ete uspeti da optimizujete pravilno i dovoljno kod u
ovom trenutku. Iskustvo moe doi iz rada sa starijim mainama, softverom, programskim
jezikom ili kompajlerom. Kada se bilo koja od ovih stvari promeni, velika je verovatnoa da
vam iskustvo nee pomoi. Nikada ne moete biti sigurni u efekte optimizacije, dok ne
testirate optimizovani kod i izmerite konkretne efekte vaih optimizacija.
Autor knjige se pre nekoliko godina bavio programom koji je trebalo da sumira
elemente dvodimenzionalnog niza, odnosno matrice. Originalni kod (C++) je izgledao ovako i
predstavlja primer klasinog (standardnog) koda za sumiranje elemenata matrice:
sum = 0;
for ( row = 0; row < rowCount; row++ ) {
for ( column = 0; column < columnCount; column++ ) {
sum = sum + matrix[ row ][ column ];
}
}
Ovaj kod je sasvim standardan, a kako su performanse sumiranja matrice bile kritine
za celu aplikaciju, autor knjige je znao da su pristupanje lanovima niza i testiranja u petlji
morala nositi sa sobom odreene penale po performanse. Znao je da svaki put kada kod
pristupa dvo-dimenzionalnom nizu, da se izvode skupa mnoenja i sabiranja. Za matrice reda
100, odnosno 100 X 100, to je bilo 10,000 mnoenja i sabiranja, plus overhead koji pravi
petlja. Autor je razmiljao, da bi konvertovanjem u pointersku notaciju mogao da inkrementira
pointere i da praktino zameni 10,000 skupih mnoenja relativno jeftinim operacijama
inkrementiranja pointera. Zatim je paljivo konvertovao gornji kod u pointersku notaciju, u
pokuaju da optimizuje sumiranje elemenata u matrici:
19/50
sum = 0;
elementPointer = matrix;
lastElementPointer = matrix[ rowCount - 1 ][ columnCount - 1 ] + 1;
while ( elementPointer < lastElementPointer ) {
sum = sum + *elementPointer++;
}
Iako novi kod nije itljiv kao prva verzija, posebno programerima koji nisu vini u C++
programskom jeziku, autor je bio veoma zadovoljan sobom. Za matricu 100 X 100, izraunao
je da je utedeo 10,000 mnoenja i overhead petlje. Bio je toliko zadovoljan sobom da je reio
da izmeri ubrzanje (to je neto to nije radio tako esto tada) da bi jo jednom mogao da oda
priznanje samom sebi.
Da li znate ta je izmerio? Nije bilo nikakvog ubrzanja. Ni sa matricom 100 X 100, niti
sa matricom 10 X 10, niti sa matricom bilo koje veliine. Bio je toliko razoaran, da je odluio
da pogleda asemblerski kod koji je generisao kompajler da bi video zato optimizacija nije dala
nikakve rezultate. Na njegovo iznenaenje, ispostavilo se da on nije bio prvi programer kome
je bilo potrebno da vri iteriranje kroz elemente niza, odnosno matrice optimizator
kompajlera je ve konvertovao pristupe nizu u pointere. Nauio je takoe, da je jedini rezultat
optimizacije, u koji moete biti sigurni loija itljivost koda, a sa njom i naporniji i dugotrajniji
rad. Ako ne uradite merenja da bi znali da li je novi kod efikasniji, onda nemojte da rtvujete
itljivost koda za neizvesnost u performansama.
Jon Bentley je, u svom klasiku Pisanje efikasnih C programa iz 1991. godine, prijavio
slino iskustvo prilikom konverzije u pointere, koje je pralktino usporilo aplikaciju za 10%.
Ista konverzija je, u drugom okruenju, popravila performanse za itavih 50%. Sve zavisi od
okruenja.
Nijedan programer nikada nije bio u mogunosti da predvidi ili analizira uska grla bez
podataka. Nije vano ta vi mislite u kom pravcu stvari idu, biete iznenaeni kada otkrijete da
idu negde drugde. - Joseph M. Newcomer.
20/50
ITERACIJE
Jednom kada ste identifikovali usko grlo, biete zapanjeni koliko je mogue poboljati
performanse putem optimizacije koda. Veoma retko ili skoro nikad ete uspeti da postignete
desetostruko ubrzanje korienjem jedne tehnike za optimizaciju koda, ali moete efektivno da
kombinujete tehnike, tako da treba nastaviti sa potragom za tehnikama koje e na vaoj
aplikaciji dati najbolja ubrzanja, ak i kada pronaete neku tehniku koja radi.
Autor knjige je jednom prilikom pisao softversku implementaciju standarda za
enkripciju podataka (Data Encryption Standard) - DES. Enkripcija prema DES-u enkodira
digitalne podatke tako da se oni ne mogu proitati bez lozinke. Algoritam za enkripciju je tako
kompleksan i uvijen da izgleda kao da je korien sam na sebi. Cilj u pogledu performansi za
navedenu DES implementaciju je bio da se enkriptuje fajl veliine 18KB za 37 sekundi na
originalnom IBM PC-u. Prva implementacija koju je autor uradio se izvravala za 21 minut i 40
sekundi, tako da je sledio neto dui put do cilja.
Iako je veina pojedinanih optimizacija predvienih za ovu konkretnu situaciju davala
manje rezultate, kada se primene sve one zajedno, bile su dovoljno dobre da se zavri
zadatak. Kada se pogledaju procentualna poboljanja, 3 ili ak 4 pojedinane optimizacije koje
su uraene ne bi ispunile cilj. Meutim, konana kombinacija optimizacija je ispunila cilj.
Poruka ove prie jeste da ako kopate dovoljno duboko, moete postii fantastine dobitke.
Optimizacija koda koju je uradio autor implementacije je, prema njegovim reima,
najagresivnija optimizacija koda koju je ikada uradio. Istovremeno, finalni kod je bio
najneitljiviji i kod najtei za odravanje koji je ikada napisao. Sam inicijalni algoritam za
enkripciju je dovoljno komplikovan. Kod koji je nastao optimizacijom koda jezika visokog nivoa
je bio jedva itljiv. Transformacija u asemblerski kod je recimo kreirala jednu rutinu od 500
linija koda koje bio trebalo da se uplai svaki prosean, odnosno normalan programer.
Generalno, odnos koji smo pominjali izmeu optimizacije i itljivosti koda vai tj. obrnuto je
proporcionalan. Pogledajmo tabelu primenjenih optimizacija za implementaciju DES algoritma:
Optimizacija
Vreme izvravanja
Poboljanje
Inicijalna implementacija
21:40
21/50
22/50
LOGIKA
Veliki deo programiranja se sastoji od manipulisanja logikom. Pogledajmo nekoliko
tehnika kojim se efikasnije upravlja logikom programa.
23/50
operatori su deo standardnih operatora jezika C++ i javljaju se jo recimo u jeziku Java kao
uslovni operatori. Ako programski jezik u kojem radite standardno (natively) ne podrava
kratko-spojene operatore, treba da izbegavate korienje logikih strujtura I (and) i ILI (or),
dodajui drugaiju logiku. Gornji primer bi mogao da izgelda ovako:
if ( 5 < x ) then
if ( x < 10 ) then ...
Princip koji govori da prestanete sa testiranjima i proverama nakon to sigurno znate
njihov konani rezultat je dobar i za veliki broj drugih situacija u programiranju. Kao jedan
takav primer moe posluiti i petlja za pretragu. Ako skenirate niz unetih brojeva i traite
negativnu vrednost, a jednostavno treba da znate da li u tom nizu postoji negativan broj,
jedan od pristupa jeste da proverite sve unete vrednosti i da u neku pomonu promenljivu
(recimo negativeNumberFound) unesete vrednost true kada pronaete negativnu vrednost.
Ako takav niz ima neku (ili vie negativnih vrednosti) na kon petlje imaemo informaciju da li
je pronaena bilo koja negativna vrednost. Evo kako bi takva petlja za pretragu izgledala u
programskom jeziku C++:
negativeNumberFound = false;
for ( i = 0; i < count; i++ ) {
if ( input[ i ] < 0 ) {
negativeNumberFound = true;
}
}
Bolji pristup bi bio da se prekine sa skeniranjem (petljom) im se pronae neka
negativna vrednost. Bilo koja od sledeih konstrukcija bi reila opisanu radnju:
Jezik
C++
Java
Standardno vreme
Vreme sa optimizovanim kodom
4.27
3.68
4.85
3.46
Tabela 9. Rezultati korienja break naredbe
Uteda u vremenu
14%
29%
24/50
Rezultati merenja su nastali kao plod izvravanja nekoliko hiljada ili miliona prolaza
kroz specifilne delove koda da bi se odbacile fluktuacije rezultata koje se javljaju od
testa do testa.
Precizne marke i verzije kompajlera nisu naznaene. Performanse znaajno variraju od
proizvoaa do proizvoaa i od razliitih verzija kompajlera.
Poreenja rezultata razliitih programiskih jezika nemaju uvek smisla zbog toga to
kompajleri za razliite jezike ne nude uvek uporedive opcije za generisanje koda.
Neke od uteda u vremenu nisu mogle biti prikazane na pravilan nain zbog
zaokruivanja u samoj tabeli u kolonama za standardno i vreme sa optimizovanim
kodom.
25/50
Select inputCharacter
Case "A" To "Z", "a" To "z"
ProcessAlpha( inputCharacter )
Case " "
ProcessSpace( inputCharacter )
Case ",", ".", ":", ";", "!", "?"
ProcessPunctuation( inputCharacter )
Case "0" To "9"
ProcessDigit( inputCharacter )
Case "+", "="
ProcessMathSymbol( inputCharacter )
Case Else
ProcessError( inputCharacter )
End Select
Zbog toga to se najei sluajevi pronalaze ranije u optimizovanom kodu, ukupan
efekat e biti poboljane performanse usled manjeg broja testiranja. U sledeoj tabeli moemo
pogledati rezultate optimizacije sa tipinim miksom ulaznih karaktera:
Vreme sa prvim
Vreme sa
Utede u vremenu
kodom
optimizovanim kodom
C#
0.220
0.260
-18%
Java
2.56
2.56
0%
Visual Basic
0.280
0.260
7%
Tabela 10. Merenja su izvrena sa ulaznim skupom: 78% slova, 17% blanko i 5% interpunkcijskih karaktera
Jezik
Rezultati merenja su oekivani za Visual Basic, ali za jezike Java i C# nisu. Oigledno,
ovakvi rezultati su posledica naina na koji je strukturirana case naredba u jezicima C# i Java
zbog toga to se svaka vrednost mora enumerisati pojedinano, a nije ih mogue
enumerisati u intervalima (kao recimo u Visual Basic-u), pa zbog toga kod C# i Java koda
nema poboljanja usled optimizacije. Opet, ovakav rezultat naglaava vanost tvrdnji da ne
treba slepo pratiti bilo koji vid predloene optimizacije i da e konkretne implementacije
kompajlera znaajno uticati na rezultate. Gledajui ovo, mogli bi da pretpostavite e kod koji
generie kompajler Visual Basic-a za skup if-then-else naredbi koje vre isti test biti slian, ako
ne i isti sa prethodnim. Pogledajmo sada nove rezultate za if-then-else konstrukciju:
Vreme sa prvim
Vreme sa
Utede u vremenu
kodom
optimizovanim kodom
C#
0.630
0.330
48%
Java
0.922
0.460
50%
Visual Basic
1.36
1.00
26%
Tabela 11. Vremena dobijena korienjem if-then-else umesto case konstrukcije na istom setu ulaznih karaktera
Jezik
Odmah se vidi da su rezultati prilino razliiti. Za isti broj testova, kompajleru Visual
Basic-a je potrebno oko 5 puta vie vremena u neoptimizovanom sluaju, a oko 4 puta vie u
optimizovanom sluaju. Ovo govori da kompajer generie potpuno drugaiji asemblerski kod za
sluaj sa case naredbom od sluaja za if-then-else naredbu.
26/50
case
if-then-else
Utede u vremenu
Odnos performansi
0.260
0.330
-27%
1:1
2.56
0.460
82%
6:1
0.260
1.00
258%
1:4
Tabela 12. Poreenje if-then-else i case konstrukcija sa optimizovanim kodom
Ovi rezultati se kose sa bilo kakvim logikim objanjenjem. U jednom od jezika, case
naredba je superiorna u odnosu na if-then-else; u drugom jeziku, if-then-else je dramatino
superiorna u odnosu na case. U treem jeziku, ta razlika je relativno mala. Moete razmiljati
da e zbog toga to C# i Java dele slinu sintaksu za case naredbu, njihovi rezultati biti slini,
ali oni su praktino suprotni jedan drugom. Ovaj primer jasno ilustruje teinu prihvatanja bilo
kakvog pozitivnog principa, odnosno logike za optimizaciju koda prosto ne postoji
pouzdana zamena za merenje rezultata.
27/50
kategorije
na
osnovu
<-- 1
...
category = categoryTable[ a ][ b ][ c ];
Iako je tabela neto tea za razumevanje, ona je dobro dokumentovana i nee biti
nita tea za itanje od komlikovanog lanca logike. Ako se definicija promeni, tabela e biti
daleko laka za odravanje od komplikovane logike. Evo rezultata performansi:
Jezik
C++
Visual Basic
Osnovno
Vreme sa optimizovanim
Uteda
vreme
kodom
5.04
3.39
33%
5.21
2.60
50%
Tabela 13. Tabela umesto komplikovanog lanca logike
Odnos
performansi
1.5:1
2:1
28/50
Lenja procena se zasniva na principu koji je koristio cimer. Ako program koristi lenju
procenu, on izbegava da vri operacije, odnosno da vri neki posao, sve do onog trenutka kada
je stvarno neophodno da se posao uradi. Lenja procena je slina just-in-time strategijama koje
obavljaju posao samo trenutak pre nego to je njihov rezultat neophodan.
Pretpostavimo, na primer, da va program sadri tabelu sa 5000 vrednosti i da
generie kompletnu tabelu prilikom startovanja. Pretpostavimo i da koristi vrednosti iz te
tabele tokom svog izvravanja. Ako program koristi samo mali broj od ovih 5000 unosa iz
tabele, moda bi imalo vie smisla da ih raunate tek u onom trenutku kada je potrebno da se
iskoriste, pre nego da ih inicijalizujete sve odjednom. Jednom, kada se unos izrauna, on se
moe sauvati u posebnom delu namenjenom za koriene vrednosti za budue potrebe (inae
poznato kao keiranje). Na ovaj nain se ne vre raunanja onih vrednosti koja nee biti
koriena u programu, ime se skrauje ukupno vreme izvravanja aplikacije, a i bolje
distribuira optereenje resursa.
PETLJE
Usled injenice da se petlje izvravaju veliki broj puta, uska grla u programima se
esto nalaze ba u petljama. Pogledaemo nekoliko tehnika koje petlje ine brima.
RAZBIJANJE CIKLUSA (UNSWITCHING)
Prebacivanje (switching) se odnosi na donoenje odluka unutar petlji svaki put kada se
petlja izvri. Ako se odluka ne menja tokom izvravanja petlje, moete da razbijete petlju
(unswitch) donoenjem odluke izvan petlje. Obino, ovo zahteva okretanje petlje iznutra ka
spolja, odnosno stavljenje petlje unutar uslova umesto stavljenje uslova unutar petlje. Evo
jednog primera petlje pre razbijana u jeziku C++:
for ( i = 0; i < count; i++ ) {
if ( sumType == SUMTYPE_NET ) {
netSum = netSum + amount[ i ];
}
else {
grossSum = grossSum + amount[ i ];
}
}
U gornjem kodu, upit if (sumType == SUMTYPE_NET) se ponavlja u svakoj iteraciji
petlje, iako e rezultat biti isti svaki put kada se proe kroz petlju. Ako imamo takvu situaciju,
moemo da preradimo gornju petlju na sledei nain, mada moramo imati na umu da ovakva
konstrukcija naruava nekoliko pravila dobrog programiranja, a to su recimo itljivost koda i
lakoa odravanja:
if ( sumType == SUMTYPE_NET ) {
for ( i = 0; i < count; i++ ) {
netSum = netSum + amount[ i ];
}
}
else {
for ( i = 0; i < count; i++ ) {
grossSum = grossSum + amount[ i ];
}
}
29/50
Loa strana ovakvog reenja lei u injenici da se dve petlje moraju odravati
paralelno. Ako se recimo promenljiva count promeni u promenljivu clientCount, morate je
promeniti na dva mesta. Ovo morate drati na umu to zna da bude umarajue, a i poveava
anse za grekama, kao i to da komplikuje odravanje. Ovaj primer takoe ilustruje kljuni
izazov u optimizaciji koda, a to je da efekat bilo koje konkretne optimizacije nije
predvidiv. Kao to moemo videti u tabeli 14, ovakva optimizacija daje znaajno ubrzanje od
20% u 3 programska jezika, ali ne i u Visual Basic-u. Ovakva optimizacija, iako se moe
oekivati da uvek daje konkretna ubrzanja, na ovom primeru u Visual Basic-u ne bi donela
ubrzanja, ali bi itljivost sigurno bila loija. Uvek moramo meriti rezultate optimizacija!
Jezik
C++
Java
Visual Basic
Python
Uteda u vremenu
19%
21%
<1%
28%
30/50
<-- 1
31/50
Uteda u vremenu
34%
43%
16%
-27%
Uteda u vremenu
42%
43%
31%
-12%
Gornji rezultati govore da dalje razvijanje petlje moe, ali i ne mora doneti utede u
vremenu. Ako krenete u pravcu daljeg razvoja, glavna briga postaje izgled vaeg koda. Iako
ovaj kod ne izgleda preterano komplikovano, podsetimo se da je petlja startovala sa 5 linija
koda. U svakom sluaju, odaberite pravi odnos (za va sluaj) izmeu brzine i iljivosti koda.
32/50
Standardno vreme
Vreme optimizovanog koda Uteda u vremenu
3.69
2.97
19%
2.27
1.97
13%
4.13
2.35
43%
Tabela 18. Minimiziranje koda unutar petlje, rateCount je 100
Osim za Javu (i njen kompajler), utede koje se postiu nisu prevelike, tako da tokom
inicijalnog kodiranja moete da koristite bilo koju tehniku koja je itljivija, bez brige o brzini
koda. Naravno, sve do trenutka kada brzina postane kritian faktor.
if ( found ) {
...
U gornjem kodu, svaka iteracija petlje testira delove (!found) i (i < count). Svrha testa
(!found) jeste da odredi da li je traeni element pronaen. Svrha testa (i < count) jeste da
sprei prelazak kraja niza koji se pretrauje. Unutar petlje, svaka vrednost niza item se testira
pojedinano, tako da petlja realno vri 3 testa u svakoj iteraciji.
U ovakvoj petlji za pretraivanje, moemo da kombinujemo ova tri testa na nain da
se izvri samo jedno testiranje po iteraciji stavljenjem markerske vrednosti na kraj opsega za
pretragu da bi se petlja zaustavila. U ovom sluaju, moemo jednostavno da dodelimo
vrednost koju traimo kao dodatni element odmah posle kraja opsega za pretragu (upamtimo
da ostavimo jo jedno mesto za taj element kada deklariemo sam niz). Sada, proveravamo
svaki element, od poetka do kraja niza, i ako ne naemo takav element sve do onog kojeg
33/50
smo postavili na kraj niza, znamo da vrednost koju traimo ne postoji u originalnom nizu.
Pogledajmo preraeni deo gornjeg koda sa dodatom markerskom vrednou:
// postavljamo markersku (traenu) vrednost, uvamo originalnu vrednost
initialValue = item[ count ];
item[ count ] = testValue;
<-- 1
i = 0;
while ( item[ i ] != testValue ) {
i++;
}
// proveravamo da li je pronaena vrednost (ako je i < count onda je naena)
if ( i < count ) {
...
U zavisnosti od tipa podataka koji se pretrauju, utede variraju, ali ako se radi o
recimo celim brojevima razlike mogu biti dramatine. Ova tehnika se moe upotrebiti u bilo
kojoj situaciji u kojoj koristite linearnu pretragu (bilo da su to nizovi ili povezane liste). Jedina
stvar na koju morate obratiti panju jeste izbor same markerske vrednosti, kao i to da morate
biti oprezni kako postavljate markersku vrednost u strukturu podataka.
Standardno
Vreme
Uteda u
Odnos
vreme
optimizovanog koda
vremenu
performansi
C#
0.771
0.590
23%
1.3:1
Java
1.63
0.912
44%
2:1
Visual Basic
1.34
0.470
65%
3:1
Tabela 19. Pretraga niza celih brojeva (100 elemenata) upotrebom markerske vrednosti
Jezik
Standardno vreme
Vreme optimizovanog koda Uteda u vremenu
4.75
3.19
33%
5.39
3.56
34%
4.16
3.65
12%
3.48
3.33
4%
Tabela 20. Poboljanje performansi nastalo zamenom pozicija ugnjedenih petlji
34/50
Standardno vreme
Vreme optimizovanog koda
Uteda u vremenu
4.33
3.80
12%
3.54
1.80
49%
Tabela 21. Uteda postignute smanjenjem snage operacije, 20 iteracija petlje
TRANSFORMACIJE PODATAKA
Promena tipa podataka moe da bude velika pomo u smanjenju veliine i poboljanju
brzine izvravanja programa. Dizajn struktura podataka sam za sebe je krupna i znaajna
oblast i izlazi iz domena ovoga rada, ali skromne promene u implementaciji tipova podataka
mogu znaajno uticati na performanse. Pogledaemo neke od tehnika koje mogu da pomognu
u optimizaciji aplikacija.
35/50
Standardno
Vreme
Uteda u
Odnos
vreme
optimizovanog koda
vremenu
performansi
2.80
0.801
71%
3.5:1
5.01
4.65
7%
1:1
6.84
0.280
96%
25:1
Tabela 22. Razlika u brzini petlje sa brojaem koji je ralan ili ceo broj
36/50
Jezik
C++
C#
Java
PHP
Python
Visual Basic
Standardno
Vreme
Uteda u
vreme
optimizovanog koda
vremenu
8.75
7.82
11%
3.28
2.99
9%
7.78
4.14
47%
6.24
4.10
34%
3.31
2.23
32%
9.43
3.22
66%
Tabela 23. Rezultat smanjenja broja dimenzija niza
Odnos
performansi
1:1
1:1
2:1
1.5:1
1.5:1
3:1
Rezultat ovakve optimizacije je u Visual Basicu i Javi odlian, dobar u PHP i Python-u,
ali osrednji u C++ i C#. Naravno, ne treba bit preotar prema C# jer je on ve imao najbolje
vreme sa neoptimizovanim kodom, a i nakon optimizacije je u ostao u samom vrhu. Veoma
veliki raspon rezultata i promena kod ove optimizacije jo jednom govori da savete za
optimizaciju ne treba slepo pratiti, ve konstantno meriti rezultate.
37/50
38/50
39/50
IZRAZI
Veliki deo programa se odvija unutar matematikih ili logikih izraza. to su izrazi
komplikovaniji, to vie resursa i vremena zauzimaju, pa emo pogledati naine na koje
moemo da ih uinimo jeftinijim.
Standardno
Vreme optimizovanog
Uteda u
vreme
koda
vremenu
7.43
0.010
99.9%
4.59
0.220
95%
4.21
0.401
90%
Tabela 26. Zamena uslova uz poznavanje algebarskog odnosa
Odnos
performansi
750:1
20:1
10:1
Recimo da treba da izraunate polinom. Opti kod za raunanje polinoma n-tog reda
izgleda ovako (jezik Visual Basic):
value = coefficient( 0 )
For power = 1 To order
value = value + coefficient( power ) * x^power
Next
40/50
Ako razmiljate o redukciji snage, u ovom kodu ete gledati u deo koji izvrava
stepenovanje. Jedno od reenja bi mogla da bude zamena stepenovanja mnoenjima u svakom
prolazu kroz petlju, to je praktino analaogno redukciji snage koju smo imali ranije zamena
mnoenja sabiranjem. Evo kao bi izgledalo raunanje polinoma sa redukcijom snage:
value = coefficient( 0 )
powerOfX = x
For power = 1 to order
value = value + coefficient( power ) * powerOfX
powerOfX = powerOfX * x
Next
Ovakva optimizacija daje znaajne rezultate ako radite sa polinomima drugog reda,
odnosno polinomima iji je najvii stepen kvadrat celog broja, ili sa polinomima viih redova:
Standardno
Vreme
Uteda u
Odnos
vreme
optimizovanog koda
vremenu
performansi
Python
3.24
2.60
20%
1:1
Visual Basic
6.26
0.160
97%
40:1
Tabela 27. Redukcija snage, promena operacije sa stepenovanja na mnoenje
Jezik
Meutim, tu nije kraj. Ako dalje primenite princip redukcije snage u petlji, moete da
sumirate stepene, a ne da ih mnoite svaki put, ime bi eliminisali dodatnu promenljivu
powerOfX i zamenili je sa dva mnoenja u svakom prolazu kroz petlju umesto jednog:
value = 0
For power = order to 1 Step -1
value = ( value + coefficient( power ) ) * x
Next
value = value + coefficient( 0 )
Standardno
Prva
Druga
Utede u odnosu na
vreme
optimizacija
optimizacija
prvu optimizaciju
Python
3.24
2.60
2.53
3%
Visual Basic
6.26
0.16
0.31
-94%
Tabela 28. Dalja redukcija snage, dvostruko mnoenje uz izbacivanje promenljive koja uva stepen X
Jezik
41/50
Standardno vreme
Vreme optimizovanog koda Uteda u vremenu
9.66
5.97
38%
17.0
12.3
28%
2.45
1.50
39%
Tabela 29. Rezultati inicijalizacije koriene vrednosti izvan izraza
42/50
Standardno
Vreme
Uteda u
Odnos
vreme
optimizovanog koda
vremenu
performansi
C++
9.66
0.662
93%
15:1
Java
17.0
0.882
95%
20:1
PHP
2.45
3.45
-41%
2:3
Tabela 30. Funkcija za raunanje celobrojnog logaritma osnove 2 koja radi iskljuivo sa celim brojevima
Jezik
43/50
Standardno
Vreme
Uteda u
vreme
optimizovanog koda
vremenu
2.97
0.251
92%
3.86
4.63
-20%
Tabela 31. Zamena zahtevnog dela preraunatom vrednou iz niza
44/50
Odnos
performansi
10:1
1:1
monthlyInterest
Vreme optimizovanog
koda
2.94
2.83
3.91
3.94
Tabela 32. Zamena estog podizraza promenljivom
Standardno vreme
Uteda u vremenu
4%
-1%
45/50
RUTINE
Jedan od najmonijih alata u optimizaciji koda jeste dobra dekompozicija rutina. Male,
dobro definisane rutine, tede prostor tako to posao obavljaju odvojeno, na razliitim
mestima. Takve rutine ine da je program lako optimizovati zbog toga to refaktorisanje koda
u jednoj rutini donosi poboljanje performansi na svim mestima gde se ta rutina poziva. Manje
rutine su lake za rekodiranje u jezicima nieg nivoa. Dugake, komplikovane rutine same po
sebi je dovoljno teko razumeti, a rekodiranje u jeziku nieg nivoa je prosto nemogue.
U nekim situacijama, na ovaj nain (stavljanjem koda rutine direktno na mesto poziva
iste) bi mogli da utedite par nanosekundi. Naravno, ako koristite mogunosti jezika kao to je
C++ komanda inline. Sa druge strane, ako radite u jeziku koji ne podrava komandu inline
direktno, ali ima makro predprocesor, moete da iskoristite makro da umetnete kod direktno.
Meutim, moderni raunari praktino ne kanjavaju poziv rutina. Kao to se iz gornje tabele
moe i videti, umetanjem koda rutine pomou komande inline ete pre degradirati
performanse nego ih optimizovati.
46/50
Da li ete pratiti ovaj dobro poznati pristup zavisi najvie od toga koliko ste vini sa
jezicima niskog nivoa, koliko je problem pogodan za rekodiranje u jeziku niskog nivoa, pa i od
stepena oaja u koji ste zapali. Autor knjige se prvi put sreo sa ovom tehnikom za optimizaciju
koda prilikom pisanja programa za enkripciju podataka, odnosno DES algoritma. Pre nego to
se odluio da proba i ovu tehniku, program je bio i dalje duplo sporiji od zacrtanog cilja po
pitanju brzine iako je isprobao sve mogue tehnike za koje je uo do tada. Rekodiranje dela
aplikacije u asembleru je bila jedina preostala opcija. Kao potpuni poetnik u svetu
programiranja u asembleru, sve to je mogao da pokua jeste ista translacija sa jezika
visokog nivoa na asembler. Iako je uradio ba to (istu translaciju koda), dobio je ubrzanje od
ak 50% iako se radilo o rekodiranju na tako rudimentarnom nivou! Recimo da imate rutinu
koja konvertuje binarne podatke u velike (uppercase) ASCII karaktere. Sledei primer
pokazuje kod za tu operaciju u jeziku Delphi:
procedure HexExpand(
var source: ByteArray;
var target: WordArray;
byteCount: word
);
var
index: integer;
lowerByte: byte;
upperByte: byte;
targetIndex: integer;
begin
targetIndex := 1;
for index := 1 to byteCount do begin
target[ targetIndex ] := ( (source[ index ] and $F0) shr 4 ) + $41;
target[ targetIndex+1 ] := (source[ index ] and $0f) + $41;
targetIndex := targetIndex + 2;
end;
end;
Iako je iz ovog koda dosta teko uoljivo koji deo koda je trom (fat), moe se uoiti da
se dosta bavi manipulacijom bitovima, to ba i nije jaa strana Delphi-ja. Meutim,
manipulacija bitovima jeste jaa strana asemblera, tako da ovaj deo koda predstavlja dobrog
kandidata za rekodiranje u asembleru. Sledi translirani kod u asembleru:
procedure HexExpand(
var source;
var target;
byteCount : Integer
);
label
EXPAND;
asm
MOV
MOV
MOV
XOR
EXPAND:
MOV
ECX,byteCount
ESI,source
EDI,target
EAX,EAX
//
//
//
//
EBX,EAX
// array offset
47/50
DL,[ESI+EBX]
DH,DL
AND
ADD
DH,$F
DH,$41
// get msbs
// add 65 to make upper case
SHR
AND
ADD
SHL
MOV
DL,4
DL,$F
DL,$41
BX,1
[EDI+EBX],DX
//
//
//
//
//
INC
LOOP
EAX
EXPAND
end;
Rekodiranje u asembleru je u ovom sluaju bilo prilino profitabilno, dajui ubrzanje
od ak 41%. Bilo bi logino da pretpostavimo da bi osnovni kod u jeziku viskog nivoa koji je
pogodan za manipulaciju bitovima (C++ na primer), imao manje dobitke nego Delphi, prilikom
translacije u asemblerski kod. Pogledajmo rezultate:
Jezik
C++
Delphi
Uteda
29%
41%
48/50
ZAKLJUAK
Moemo oekivati da su se atributi performansi znaajnije promenili u poslednjih 1015 godina. Na neki nain i jesu. Kompjuteri su dramatino bri nego tada, a i memorije ima u
izobilju. U to doba, kada se radila optimizacija koda, bilo je potrebno da se izvri od 10,000 do
50,000 testova na kodu da bi se dobili razumljivi, merljivi rezultati. Danas, potrebno je da se
izvri od milion do 100 miliona testova da bi se dobili isto tako merljivi rezultati. A kada je
potrebno da izvrite recimo 100 miliona testova da bi se dobio merljiv rezultat, morate da se
zapitate da li e iko ikada primetiti uticaj optimizacija u realnoj eksploataciji programa.
Raunari (hardver) su postali tako moni da su brojne optimizacije performansi o kojima smo
priali, za mnoge iroko koriene primene, postale irelevantne.
Drugim reima, problemi vezani za performanse su ostali prilino isti. Programerima
koji piu desktop aplikacije ove informacije moda vie nisu neophodne, ali onima koji piu za
embedded sisteme, sisteme u realnom vremenu i druge sisteme sa striktnim zahtevima vezano
za brzinu i memoriju mogu biti od koristi.
Potreba da se meri svaki pojedinani pokuaj optimizacije koda ostala je konstanta jo
od objavljivanja studije o Fortran programima Donalda Knuth-a 1971. godine. Prema
merenjima koje smo obradili u ovom radu, efekat bilo koje pojedinane optimizacije je u stvari
manje predvidiv nego to je to bio ranije, pre recimo 10-15 godina. Efekat svake optimizacije
koda zavisi od programskog jezika, kompajlera, verzije kompajlera, biblioteke koda, kao i
podeavanja kompajlera izmeu ostalog.
Optimizacija koda uvek podrazumeva ustupke izmeu kompleksnosti, itljivosti,
jednostavnosti i lakoe odravanja koda sa jedne strane i elje da se poboljaju performanse
na drugoj. Ona sa sobom nosi poveanje nivoa odravanja koda usled reprofilisanja koje je
neophodno.
Moe se rei da je insistiranje na merljivim poboljanjima dobar put da se oduprete
iskuenjima prerane optimizacije, kao i siguran put da se nametne pisanje istog,
standardnog koda. Ako je optimizacija dovoljno vana da prizove profajler (softver za
profilisanje softvera), onda je verovatno i dovoljno vana da bi se i dozvolila optimizacija,
naravno sve dok kod radi ispravno. Meutim, ako optimizacija nije dovoljno vana da bi
prizvala maineriju za profilisanje, onda optimizacija svakako nije dovoljno bitna da bi se zbog
nje degradirali itljivost, lakoa odravanja i druge karakteristike koda. Uticaj neizmerenih
optimizacija koda na performanse je u najbolju ruku pekulativan, dok je negativan uticaj na
itljivost koda siguran isto kao i tetni uticaji na druge kvalitativne karakteristike koda.
49/50
LITERATURA
1. Steve McConnell : Code complete, Second Edition, 2004.
2. http://www.tantalon.com/pete/cppopt/main.htm (22.08.2011.)
50/50