Mnogi softverski ininjeri preporuuju pristup odugovlaenja optimizaciji softverskog koda. Prema ovome pristupu optimizaciju treba odugovlaiti to je vie mogue,a ako je mogue da optimizaciju zaobiemo u potpunosti. Prerana ili preesta optimizacija prosto nije dobar pristup inenjerstvu. Svakako je bolje da imamo program koji radi, nego da imamo brzi program koji pada ili daje netane informacije. Ali teko da emo uspeno napisati neku aplikaciju bez bavljenja optimizacijom na nekom nivou u procesu stvaranja aplikacije. Postoji mnogo nivoa na kojima se moe vriti optimizacija performansi naih aplikacija. Neke od tehnika koje emo obraditi bolje rade na kompajliranim jezicima, neke na interpretiranim. Sutina je u tome da kakve god optimizacije primenimo, vano je da izmerimo efekat predvienih optimizacija i da to inimo redovno kako bi bili sigurni koja je optimizacija i kakve dala efekte. Optimizacija je postupak ureivanja programskog koda na nain da se on izvrava bre i manje optereuje sistemske resurse. Osim opte optimizacije, mogue je izvesti i optimizaciju koda za izvravanje na nekom tano odreenom hardverskom ureaju koji ima neke dodatne osobitosti koje ureaj drugog proizvoaa ne posjeduje. Optimizacija se uglavnom temelji na izbacivanju suvinih dijelova programskog koda, a vrlo esto i na primjeni novih funkcija i rutina koje su se pojavile od vremena nastanka programa, a naprednije su od starijih.
- 3 -
1.STRATEGIJE ZA UNAPREENJE SOFTVERSKOG KODA
Kada govorimo o strategijama za unapreenje softverskog koda, moramo obratiti panju na optimizaciju tj. Tjuning performansi. Raunarski resursi u pogledu raspoloive memorije i brzine procesora su bili ozbiljno ogranieni tokom 1960-tih, programeri su poeli da shvataju kako je njihov fokus na performansama tetio itljivosti i lakoi odravanja programskog koda, pa je optimizacija koda ( code tuning ) tih godina dobijala sve manje panje. Povratak ogranienjima performansi sa revolucijom mikroprocesora 1980-tih je ponovo doveo efikasnost u prvi plan, koja je ponovo iezla tokom 1990-tih. Tokom 2000-tih, ogranienja po pitanju memorije u ugraenom ( embedded ) softveru za ureaje kao to su mobilni telefoni i PDA ureaji, kao i vreme izvravanja interpretiranog koda su jo jednom doveli do toga da efikasnost postane kljuna tema. Kada govorimo o performansama, stvari moemo gledati sa dva nivoa: - stratekog, - taktikog. [1] 2.PERFORMANSE
Optimizacija koda (code tuning) je samo jedna od metoda kojima se moe postii poboljanje performansi. esto moemo nai druge naine da poboljamo performanse vie (i za manje vremena), a da pri tome inimo manju tetu kodu, nego to bismo to uinili optimizacijom koda. 3. PERFORMANSE I OPTIMIZACIJA KODA
Jednom kada smo izabrali efikasnost kao prioritet, bez obzira da li se ona odnosi na brzinu ili velicinu (memorijsku), trebalo bi da uzmemo u obzir nekoliko opcija pre nego to odluimo da poboljamo brzinu ili veliinu na nivou programskog koda: - Zahtevi softvera; - Dizajn softvera; - 4 - - Dizajn klasa i funkcija; - Interakcije sa operativnim sistemom; - Hardver; - Optimizacija koda (code tuning). [2]
3.1.Zahtevi softvera
Softverski zahtev je osobina koja mora biti predstavljena u cilju reavanja nekog problema iz realnog sveta. Obrauje probleme kojima e se upravljati softverski u cilju njihovoga reavanja. Moemo rei i da je ova oblast veza izmeu realnog sveta i informacionih tehnologija, jer adresira pitanja prilagoavanja i reavanja realnih poslovnih situacija putem izgradnje softverskog reenja. Osnovna karakteristika svih softverskih zahteva je da moraju biti verifabilni. Nekada je jako skupo i teko verifikovati neki zahtev. [1]
3.2.Dizajn softvera
Pojam softvera (eng. software) odnosi se na sveukupnost instrukcija, programa i procedura koji se generie, aktivira i koristi na raznorazne naine da bi omoguio hardveru da realizuje eljene poslove. Softver se sastoji od podataka smetenih na magnetnim medijumima u elektronskim memorijama. Dizajn je dvojako definisan, kao proces koji definie arhitekturu, komponente, interfejse I druge karakteristike sistema ili komponente i kao rezultat tog procesa. Ako posmatramo kao proces, dizajn softvera je aktivnost softverskog inenjerstva unutar ivotnog ciklusa razvoja softvera, gdje su softverski zahtjevi analizirani sa ciljem da se napravi opis unutranje struktrure softvera, koji e sluiti kao osnova za njegovu izradu,tj. dizajn softvera mora opisati kako je softver podeljen i organizovan na komponente i interfejse koji povezuju ove komponente. Dizajn softvera ukljuuje glavne pravce dizajna za pojedinani program, a to je uglavnom nain na koji je program podeljen u manje celine, odnosno klase. Dizajn softvera se sastoji od dve aktivnosti koje stoje izmeu softverskih zahteva i softverskog kodiranja i testiranja, a to su: dizajn softverske arhitekture koji opisuje opti nivo softverske strukture i organizacije sa identifikacijom razliitih komponenti, - 5 - detaljni dizajn softvera koji opisuje svaku komponentu dovoljno precizno da omoguava njenu izradu (kodiranje). Odreeni dizajni softvera oteavaju pisanje koda za sisteme visokih performansi. Drugi ine da pisanje koda za visoko-performansne sisteme bude lako i prirodno. Ako uzmemo 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 ininjerske jedinice ( kao to su npr. stepeni Celzijusovi ). U ovome sluaju, bez uzimanja u obzir rizika koji sa sobom nosi dizajn visokoga nivoa, programeri bi se nali u situaciji da pokuavaju da optimizuju jednaine za reavanje polinoma 13-og reda u softveru. Ali umesto toga, oni su reili problem sa drugaijim hardverom i dizajnom visokog nivoa. 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. Razni atributi se razmatraju kao vani za vrednovanje dizajna softvera. To su odrivost, prenosivost, raspoloivost za testiranje, preciznost, svrsishodnost. [3]
3.3. Dizajn klasa i rutina
Dizajn unutranjosti klasa i rutina predstavlja jos jednu od prilika da kreiramo dizajn koji je optimizovan za performanse. Jedna od kljunih stavki koja se moe iskoristiti na ovome nivou jeste izbor tipova podataka i algoritama, koji utiu kako na zauzee memorije, tako i na samu brzinu izvravanja programa. Ovo je nivo gdje moemo da izaberemo quicksort umesto bubblesort algoritma za sortiranje. [3]
3.4.Interakcije sa operativnim sistemom
Ako nai programi rade na externim fajlovima, dinamikom memorijom ili izlaznim ureajima, velika je ansa da vre i odreene interakcije sa operativnim sistemom. Ako performanse takve aplikacije nisu zadovoljavajue postoji velika verovatnoa da su uzrok tome rutine operativnog sistema koje su ili spore ili preglomazne. U nekim situacijama neemo biti svjesni da program interreaguje sa operativnim sistemom: - ponekad kompajler generie sistemske pozive ili - nase biblioteke pozivaju sistemske rutine na takav nain da to ne bismo mogli ni da pretpostavimo. [3]
- 6 - 3.5.Kompilacija koda
Dobri kompajleri pretvaraju programski kod jezika visokog nivoa u dobro optimizovani mainski kod. Ako izaberemo odgovarajui kompajler, neemo morati da brinemo o optimizacijama na nivou koda,ali i sitno doterivanje koda sa ciljem da se poboljaju performanse samo usporiti program, jer emo na taj nain samo zbuniti kompajler. [2]
3.6.Hardver
Pojam hardver (u nasem jeziku se koristi i izraz tehnika podrka) odnosi se na opremu koja predstavlja fiziku (materijalnu) realizaciju bilo kog sistema koji obavlja odreene funkcije. Odnosi se, na merne ureaje, magnetske medijume, elektronske komponente, linije za vezu opremu. Hardver predstavlja, fizike komponente koje je potrebno instalirati i ukljuiti da bi raunar proradio. Ponekad je najjeftiniji, najistiji i najbolji nain da poboljamo performanse programa kupovina novog hardvera. Ako distribuiramo aplikaciju za iroku upotrebu korisnika kupovina novog hardvera nije realistina opcija. Ali, ako razvijemo softver po narudbini za nekoliko korisnika u jednoj kompaniji, nadogradnja hardvera je moda najjeftinija opcija. Nadogradnja hardvera tedi poetno ulaganje u merenje performansi, tedi budue trokove odravanja, a takoe i poboljava peformanse svih aplikacija koje rade na tom hardveru. [4]
3.7.Optimizacija koda
Optimizacija je postupak ureivanja programskog koda na nain da se on izvrava bre i manje optereuje sistemske resurse. 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. Ne odnosi se na krupnije promene u dizajnu koda ili druge vidove poboljanja performansi visokog nivoa.
Optimizacija koda svakako nije najefikasniji nain da se poboljaju performanse, tj. arhitektura programa, dizajn klasa. Takoe, to nije ni najlaki nain da se poboljaju performanse kupovina novog hardvera ili kompajlera sa boljom optimizacijom je mnogo laki nain. A nije ni najjeftiniji nain da se poboljaju performanse potrebno je znatno vie vremena da se programski kod runo podesi, s tim da je takav kod kasnije mnogo tee odravati. Meutim, podeavanje koda je privlano iz nekoliko razloga. Dobro je, kada uzmemo jednu liniju koda koja se izvrava za 40 milisekundi, promeniti par linija koda i tako smanjiti vreme izvravanja na 4 milisekunde. [2] - 7 - 4.BAPSKE PRIE
Smanjenje broja linija koda u jezicima visokog nivoa poboljava brzinu ili smanjuje veliinu rezultujueg mainskog koda - neistina. Mnogi programeri veruju da ako napiu kod koji ima samo jednu liniju koda ili par linija, da e takav kod biti najefikasniji mogui. Npr. kod koji inicijalizuje niz od 10 elemenata: f or i = 1 t o 10 a[ i ] = i end f or Da li biste rekli da je ovakav kod bri ili sporiji od naredne verzije koda koja radi isti posao ? a[ 1 ] = 1 a[ 2 ] = 2 a[ 3 ] = 3 a[ 4 ] = 4 a[ 5 ] = 5 a[ 6 ] = 6 a[ 7 ] = 7 a[ 8 ] = 8 a[ 9 ] = 9 a[ 10 ] = 10 Ako bismo primenili gore navedenu dogmu da je krai kod i bri , pomislili bismo da je gornji kod bri. Meutim, testovi u Microsoft Visual Basic-u i Java-i pokazuju da je drugi deo koda bri za oko 60 % od prvog. U narednoj tabeli moemo pogledati rezultate:
Tabela 1. Inicijalizacija vrednosti niza od 10 elemenata Jezik For petlja Runa inicijalizacija Uteda Odnos performansi Visual Basic 8.47 3.16 63% 2.5:1 Java 12.6 3.23 74% 4:1
Ovo svakako ne znai da poveanje broja linija koda jezika visokoga nivoa uvek poboljava brzinu ili smanjuje veliinu mainskog koda. Ali ovo govori da bez obzira na estetsku lepotu pisanja neega sto ima manje linija, ne postoji nikakav predvidiv odnos izmeu broja linija koda u jezicima visokoga nivoa i konane veliine i brzine ukupnog programa. Kod treba optimizovati tokom rada-neistina. Postoji teorija koja kae da ako elimo napisati najbri i najmanji mogui kod, trebalo bi da optimizujemo kod tokom pisanja svake rutine, a program e biti bri i manji. Ovo su neki od problema optimizacije koda tokom rada: - 8 - - programeri koji optimizuju tokom normalnog programiranja, u proseku, provedu 96% vremena na optimizaciju koda koji uopte nije potrebno optimizovati, - pogoranje perfomansi, - fokusiranje na optimizaciju tokom inicijalnog razvoja programa odvlai panju sa postizanja drugih programskih ciljeva. Programeri vie svoje panje posvete u analizu algoritma. Drugi ciljevi kao to su ispravnost koda, sakrivanje informacija i itljivost koda postanu sekundarni ciljevi, iako je performanse kasnije lake poboljati nego druge navedene ciljeve. Naknadni rad na poboljanju performansi obino utice na manje od 5% programskog koda. Ukratko, glavni nedostatak prerane optimizacije je njen nedostatak perspektive. rtve prerane optimizacije su konana brzina koda, atributi performansi koji su vaniji od brzine koda, kvalitet programa i konano korisnici programa. Povremeno, optimizacije koda nakon zavretka programa nee biti dovoljne kako bi se ispunili ciljevi po pitanju performansi. Ako je ipak potrebno da optimizujemo program pre nego to je zavren, minimizirajmo rizik tako to emo ukljuiti perspektivu u ovaj proces. Jedan od naina da to uradimo jeste da specifiramo u pogledu veliine i brzine za pojedinane delove programa i da ih onda optimizujemo tako da ispune zadate ciljeve dok radimo na ukupnom programu. Brzina programa je podjednako vana kao i tanost- neistina. Vrlo retko e se desiti da je potrebno da program bude manji ili bri pre nego korektan, odnosno ispravan. [5]
5.KADA OPTIMIZOVATI
Potrebno je da se koristi dizajn visokog kvaliteta. Napraviti da program radi tano. Da bude modularan i lako izmenljiv tako da moemo da radimo lako sa njim kasnije. Kada je program zavren i taan, potrebno je proveriti performanse. U sluaju da je program spor ili glomazan, potrebno je da ga uinimo efikasnijim. Ali, nije potrebno da optimizujemo sve dok nismo sigurni da to i treba da uradimo. Jackson-ovo pravilo optimizacije: - nemojte da radite optimizaciju, - nemojte da je radite jo uvek, tj. sve dok nemate savreno jasno i neoptimizovano reenje ( aplikaciju ).
- 9 - 5.1.Optimizacija kompajlera
Optimizacije modernih kompajlera su moda monije nego to bismo oekivali. Kada kupujemo kompajler, potrebno je da uporedimo performanse svakog kompajlera na naoj aplikaciji. Svaki kompajler ima razliite prednosti i slabosti i neke od njih e vie odgovarati aplikaciji ili tipu aplikacije koju izraujemo. Optimizacije kompajlera su efikasnije kada se radi sa standardnijim kodom nego kada kompajler radi sa optimizovanim kodom. Ako smo u kodu radili sa pametnim stvarima kao to je igranje sa indeksima petlji, naem kompajleru e biti mnogo tee da uradi svoj posao, a na program e zbog toga trpeti. Sa dobrim optimizacijama kompajlera, na kod moe imati ubrzanje od 40% ili vie. Mnoge od tehnika za optimizaciju koda (koje emo pogledati kasnije), mogu da proizvedu maksimalan dobitak od 15% do 30%. 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 kopmpajlirane sa iskljuenim optimizacijama za prvo kompajliranje i ukljuenim za drugo kompajliranje. Neki kompajleri bolje optimizuju nego drugi, dok neki bolje rade bez optimizacije. Neke Javine virtuelne maine (JVM) su bolje nego druge, ali da bi dobili najbolje rezultate za nae aplikacije, moramo da proverimo kompajler, JVM ili oba da bi izmerili efekte. [4]
Tabela 2. Brzina izvravanja koda kompajliranog sa optimizacijama i bez optimizacija kompajlera
Jezik Vreme bez optimizacija kompajlera Vreme sa optimizacijom kompajlera Uteda u vremenu Odnos performansi 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
- 10 - 6.VRSTE ZAGUENJA
Prilikom rada optimizacije koda nailazimo na delove programa koji su spori ili su veliki i menjati ih tako da budu brzi i toliko mali, tako da se mogu sakriti u pukotine izmeu bajtova u RAM-u. Uvek moramo da profiliemo program, da bismo znali koji su delovi spori i veliki. [4]
6.1.esti izvori neefikasnosti
Ulazno/Izlazne operacije: Jedan od najznaajnijih i najeih izvora neefikasnosti su nepotrebne I/O operacije. Ukoliko imamo mogunost izbora da radimo sa fajlovima u memoriji nasuprot fajlovima na disku, u bazi podataka ili preko mree, uvek koristimo strukturu podataka u radnoj memoriji , osim u situaciji kada je prostor kritian. Evo poreenja performansi izmeu koda koji pristupa nasuminim elementima u nizu od 100 elemenata koji se nalazi u radnoj memoriji i koda koji pristupa nasuminim elementima u fajlu sa 100 elemenata koji se nalazi na hard disku:
Tabela 3. Vremena pristupa nasuminim elementima, niz od 100 elemenata Jezik Vreme za fajl Vreme za RAM Uteda u vremenu Odnos performansi C++ 6.04 0.000 ~100% N/A C# 12.8 0.010 ~100% 1000:1
Prema izmerenim podacima, pristup elementima niza u memoriji je reda veliina 1000 puta bra od pristupa podacima u eksternom fajlu. Sa C++kompajlerom koji je koriten na testu, neophodno vreme koje je bilo potrebno za pristup podacima u memoriji je tako kratko da ga nije mogue izmeriti. Na sledeoj slici je prikazano poreenje performansi sa sekvencijalnim pristupom elementima:
Tabela 4. Vremena sekvencijalnog pristupa elementima niza, testovi izvreni nad 13 puta veim podacima Jezik Vreme za fajl Vreme za RAM Uteda u vremenu Odnos performansi C++ 3.29 0.021 ~99% 150:1 C# 2.60 0.030 ~99% 85:1
- 11 - Da je test koristio sporiji medijum za eksterni pristup, na primer tvrdi hard disk preko mrene konekcije razlika bi bila jo znaajnija. Test performansi u situaciji kada koristimo slian (sluajan) pristup elementima na lokaciji na mrei umesto na lokalnoj maini izgleda ovako: Tabela 5. Vremena sekvencijalnog pristupa elementima niza na mrenoj lokaciji Jezik Vreme za fajl Vreme za RAM Uteda u vremenu C++ 6.04 6.64 -10% C# 12.8 14.1 -10%
Naravno, ovi rezultati mogu da variraju drastino u zavisnosti od brzine nae mree, optereenja, udaljenosti lokalne maine od tvrdog diska kome se pristupa, brzine mrenog diska u odnosu na lokalni disk i drugih faktora... Sveukupno gledano, efekat pristupa podacima u radnoj ili jo bolje ke (cache) memoriji je dovoljno snaan da u svakoj situaciji razmislimo o I/O operacijama u kritinim delovima programa kod kojih se zahteva brzina, odnosno efikasnost.
Stranienje (paging): Operacija koja zahtjeva od operativnog sistema da menja stranice radne memorije je daleko sporija od operacije koda, radi samo sa jednom stranicom memorije. Ponekad sitna promena ima za posledicu velike razlike. U sledeem primeru je prikazana petlja za inicijalizaciju koja je proizvela veliki broj pogrenih memorijskih stranica (page faults), na sistemu koji koristi stranice veliine 4KB. (primer je uraen u Java programskom jeziku). [1] f or ( col umn = 0; col umn < MAX_COLUMNS; col umn++ ) { f or ( r ow = 0; r ow < MAX_ROWS; r ow++ ) { t abl e[ r ow ] [ col umn ] = Bl ankTabl eEl ement ( ) ; } } Na prvi pogled ovo izgleda sasvim lepo formatirana petlja sa dobrim imenima promenljivih. Ali ipak nije... Problem je u tome to je svaki element tabele dugaak 4000 bajtova. Ako tabela ima veliki broj redova, svaki put kada program pristupa drugom redu, operativni sistem e morati da promeni memorijsku stranicu. f or ( r ow = 0; r ow < MAX_ROWS; r ow++ ) { f or ( col umn = 0; col umn < MAX_COLUMNS; col umn++ ) { t abl e[ r ow ] [ col umn ] = Bl ankTabl eEl ement ( ) ; } } - 12 - Ovaj kod, kao i prethodni, takoe proizvodi pozive ka razliitim memorijskim stranicama, ali on menja redove samo MAX_ROWS puta, umesto MAX_ROWS *MAX_COLUMNS puta. Sistemski pozivi (System calls): Pozivi sistemskih rutina su esto veoma skupi. Oni esto ukljuuju promenu konteksta snimanje stanja programa. Sistemske rutine ukljuuju I/O operacije sa diskovima, tastaturom, ekranom, tampaem ili drugim ureajima, zatim rutine koje upravljaju memorijom, kao i odreene pomone rutine. Ako su performanse bitne, treba pogledati koliko su skupi sistemski pozivi koje koristimo. Ako shvatimo da oni troe znaajne resurse moemo uzeti u obzir sledee opcije: - Napisati sopstvene servise. Ponekad je potreban samo mali deo funkcionalnosti koje nam daje sistemska rutina. Moemo da kreiramo sopstveni servis koji bi zadovoljio iskljuivo nae potrebe, bez obimnijeg koritenja resursa, - Izbegavati odlazak u sistem, - Saraivati 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 performanse. Jer su oni veoma zainteresovani za takve stvari. [3] Interpretirani jezici: Interpretirani jezici proizvode znaajne gubitke u performansama zbog toga to moraju da procesiraju svaku programsku instrukciju pre nego to naprave i izvre mainski kod. U sledeoj tabeli se moe vidjeti relativan odnos u brzini izvravanja nekoliko standardnih i par interpretiranih jezika:
Tabela 6. Relativan odnos u brzini razliitih tipova jezika Programski jezik Tip programskog jezika Vreme izvravanja u odnosu na C++ C++ kompajlirani 1:1 Visual Basic kompajlirani 1:1 C# kompajlirani 1:1 Java kompajlirani 1.5:1
Kao to se moe vidjeti, C++, Visual Basic i C# imaju sline performanse. Java je tu negdje blizu, ali sporija nego pomenuti kompajlirani jezici. Greke: Poslednji standardni izvor problema sa performansama su greke u kodu. One ukljuuju zaboravljanje ukljuenog moda za proveru i pronalaenje greaka (debugging) i posledine radnje koje troe resurse (kao to su zapisivanje informacija o logovima u fajlu), - 13 - zaboravljanje da se dealocira, odnosno oslobodi radna memorija, lo dizajn tabela baze podataka... 7.PRISTUP OPTIMIZACIJI KODA
Prilikom razmatranja da li optimizacija koda moe da uini efikasnijom aplikaciju, trebalo bi da se prate sledei koraci: - Razviti softver koritenjem dobro poznatih tehnika za pisanje softverskog koda koji je razumljiv i lak za modifikaciju, - Ako su performanse loe onda je potrebno da se snimi radna verzija koda tako da se u svakom trenutku moe vratiti na poslednje dobro stanje, - Testiranje sistema: utvrditi da li su slabe performanse posledica neadekvatnog dizajna, tipova podataka i da li bi optimizacija koda mogla da da eljene rezultate. U sluaju da optimizacija koda ne bude svrsishodna, potrebno je da se vrati na korak 1, meriti svaku optimizaciju, jednu po jednu, ako optimizacija ne pobolja performanse, potrebno je da se vrati na kod koji je sauvan u prvom koraku. U veini sluajeva, vie od polovine pokuanih optimizacija e proizvesti zanemarljiva poboljanja, ponoviti proceduru od drugog koraka. [2]
7.1.Tehnike za optimizaciju koda
Optimizacija koda je oduvek bila popularna tema, praktino kroz itavu istoriju kompijuterskog programiranja. Zbog toga, ako mislimo da je potrebno da poboljamo performanse naeg programa na nivou koda (putem optimizacije koda), a imajui da ovo nekada nije ni najefikasniji niti najbri nain da se poboljaju performanse, imaemo na raspolaganju bogat skup tehnika kojima je mogue postii znaajna poboljanja performansi. Kada se govori o performansama, obino se misli na brzinu i veliinu, kako radne memorije, tako i na tvrdom disku. Treba imati u vidu da smanjenje veliine pre dolazi od redizajna klasa i rutina, kao i tipova podataka, nego od same optimizacije koda. Optimizacija - 14 - koda se praktino odnosi na promene koda manjih razmera,a ne na redizajn aplikacije irokih razmera. Neke od tehnika za optimizaciju koda se kozmetiki mogu uiniti veoma slinim odreenim refaktorisanjima koda, ali treba napraviti jasnu razliku refaktorisanje koda predstavlja promenu koda koja poboljava internu strukturu programa, dok bi se optimizacija koda u cilju poboljanja performansi mogla shvatiti kao anti-refaktorisanje. Promene koda koje nastaju optimizacijom degradiraju internu strukturu u zamenu za dobitke na polju performansi.[2]
7.2.Logika
Veliki deo programiranja se sastoji od manipulisanja logikom. Ovo su neke od tehnika kojim se efikasnije upravlja logikom programa. - Prekinuti sa ispitivanjem uslova kada se sazna konana vrednost Ako imamo ovaku naredbu:
i f ( 5 < x ) and ( x < 10 ) t hen . . .
Kada se utvrdi da je x vee od 5, nije potrebno da se testira drugi deo izraza koja god da je vrednost ostatka logikog izraza, ukupan izraz e imati vrednost false.Neki jezici imaju konstrukcije za utvrivanje vrednosti celoukupnog izraza poznato kao kratko-spojeni operatori, to znai da kompajleri generiu kod koji automatski prekida testiranje uslova im sazna konanu vrednost iskaza. Kratko-spojeni operateri su deo standardnih operatora jezika C++i javljaju se jo u jeziku Java kao uslovni operatori. Ako programski jezik u kojem radimo standardno ne podrava kratko-spojene operatore, treba da izbegavamo koritenje logikih struktura I (and) i ILI (or), dodajui drukiju logiku. Onda bi navedeni primer izgledao ovako:
i f ( 5 < x ) t hen i f ( x < 10 ) t hen . . . Princip koji govori da prestanemo sa testiranjem i proverama nakon to sigurno znamo da je njihov konaan rezultat dobar i za veliki broj drugih situacija u programiranju. Kao primer za to moe posluiti i petlja za pretragu. Ako skeniramo niz unetih brojeva i traimo negativnu vrednost,a jednostavno treba da znamo da li u tom nizu postoji negativan broj, jedan od pristupa jeste da proverimo sve unete vrednosti i da u neku pomonu promenljivu (recimo - 15 - negativeNumberFound) unesemo vrednost true kada pronaemo negativnu vrednost. Ako takav niz ima neku (ili vie negativnih vrednosti) nakon petlje imaemo informaciju da li je pronaena bilo koja negativna vrednost. Evo kako bi takva petlja za pretragu izgledala u programskom jeziku C++:
negat i veNumber Found = f al se;
f or ( i = 0; i < count ; i ++ ) { i f ( i nput [ i ] < 0 ) { negat i veNumber Found = t r ue; } } Bolji pristup bi bio da se prekine sa skeniranjem (petljom) im se pronae neka negativna vrednost. Bilo koja od sledeih konstrukcija bi reila taj problem: - Dodati break naredbu nakon to promjenljiva negativeNumberFound dobije vrednost true, - Ako jezik koji koristimo nema break naredbu, potrebno je zamjeniti break naredbu sa nekom konstrukcijom koja seli tok izvravanja programa na prvu liniju nakon petlje u trenutku kada se pronae negativna vrednost, - Promeniti for petlju while petljom i proveriti vrednost promjenljive negativeNumberFound dok proveravamo i trenutno stanje brojaa petlje, - Promeniti for petlju while petljom i postaviti jedan marker-negativnu vrednost u prvi lan niza koji se nalazi iza poslednjeg unetog broja i jednostavno proveriti postojanje negativne vrednosti u proveri uslova while petlje. Nakon to se petlja zavri, proveriti da li je pozicija prve pronaene negativne vrednosti u nizu ili nakon kraja niza. U sledeoj tabeli mogu se vidjeti rezultati postignuti koritenjem break naredbe u C++i Java: Tabela 7. Rezultati koritenja break naredbe Jezik Standarno vreme Vreme sa optimizovanim kodom Uteda u vremenu C++ 4.27 3.68 14% Java 4.85 3.46 29%
- Vremena u ovoj tebeli su data u milisekundama. Stvarna vremena e varirati u zavisnosti od kompajlera, koritenjem opcija kompajlera, kao i okruenja u kome se izvode testovi, - 16 - - Rezultati merenja su nastali kao plod izvravanja nekoliko hiljada ili miliona prolaza kroz delove koda da bi se odbacile fluktuacije rezultata koje se javljaju od testa do testa, - Poreenja rezultata razliitih programskih jezika nemaju uvek smisla, zbog toga to kompajleri za razliite jezike ne nude uvek uporedive opcije za generisanje koda.
-Poredati uslove po uestalosti ispunjavanja Poredati uslove tako da se onaj koji je najbri i koji e najvjerovatnije biti istinit, prvi izvrava. U sutini, potrebno je da se poredaju tako da najee ispunjeni uslovi budu podrazumevani sluajevi, dok one koji su najzahtjevniji i manje koriteni trebaju biti procesirani u izuzetnim sluajevima. Ovo se odnosi na case naredbu i if-then-else konstrukcije. U sledeem primeru select-case naredbe moemo vidjeti kako odgovara na unos sa tastature u procesor teksta u programskom jeziku Visual Basic kod kojeg je loe izveden redosled logikih naredbi: Sel ect i nput Char act er
Case "+", " =" Pr ocessMat hSymbol ( i nput Char act er )
Case "0" To " 9" Pr ocessDi gi t ( i nput Char act er )
Case ", ", " . " , ": ", " ; " , "! ", " ?" Pr ocessPunct uat i on( i nput Char act er )
Case " " Pr ocessSpace( i nput Char act er )
Case "A" To " Z" , "a" To " z" Pr ocessAl pha( i nput Char act er )
Case El se Pr ocessEr r or ( i nput Char act er )
End Sel ect Kod case narebdi, efekat je esto veoma slian veem setu if-then-else naredbi, tako da ako sa unosa dobijemo simbol a, program prvo testira da li je uneti znak matematiki simbol, pa znak interpunkcije, cifra, pre nego to utvrdi da se u stvari radi o alfabetskom karakteru. Poboljanje sa if-then-else konstrukcijom je konzistentnije nego sa case naredbom. [5]
- 17 - -Koristiti procenu Ako program koristi procenu, onda izbjegava da vri operacije, odnosno da vri neki posao, sve do onoga trenutka kada je stvarno neophodno da se posao uradi. Procena je slina just-in-time strategijama koje obavljaju posao samo trenutak pre nego to je njihov rezultat neophodan. Na primer, ako na program sadri tabelu sa 2000 vrednosti i da generie kompletnu tabelu prilikom startovanja i da koristi vrednosti iz te tabele tokom svog izvravanja. Ako program koristi samo mali broj od ovih 2000 unosa iz tabele, moda bi imalo vie smisla da ih raunamo tek u onom trenutku kada je potrebno da se iskoriste, pre nego da ih inicijalizujemo sve odjednom. Jednom, kada se unos izrauna, on se moe sauvati u posebnom delu namenjenom za koritenje vrednosti za budue potrebe,tj.keiranje. Na ovaj nain se ne vre raunanja onih vrednosti koja nee biti koritena u programu, ime se skrauje ukupno vreme izvravanja aplikacije, a i bolje distribuira optereenje resursa. Zbog injenice da se petlje izvravaju veliki broj puta, pogledaemo nekoliko tehnika koje petlje ine brima.
7.3.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, moemo da razbijemo petlju, donoenjem odluke izvan petlje. Obino, ovo zahteva okretanje petlje iznutra ka spolja, odnosno stavljanje petlje unutar uslova umesto stavljanje uslova unutar petlje. Evo jednog primera petlje pre razbijanja. [1] f or ( i = 0; i < count ; i ++ ) { i f ( sumType == SUMTYPE_NET ) { net Sum= net Sum+ amount [ i ] ; } el se { gr ossSum= gr ossSum+ amount [ i ] ; } }
U gornjem kodu, upit if se ponavlja u svakoj iteraciji petlje, iako e rezultat biti svaki put kada se proe kroz petlju. Ako imamo takvu situaciju, moemo da preuredimo gornju petlju na sledei nain, mada moramo imati na umu da ovakva konstrukcija naruava nekoliko pravila dobrog programiranja, kao to su itljivost koda i lakoa odravanja: - 18 - i f ( sumType == SUMTYPE_NET ) { f or ( i = 0; i < count ; i ++ ) { net Sum= net Sum+ amount [ i ] ; } } el se { f or ( i = 0; i < count ; i ++ ) { gr ossSum= gr ossSum+ amount [ i ] ; } }
Loa strana ovakvog reenja je u injenici da se dve petlje moraju odravati paralelno. Ako se promenljiva count promjeni u promenljivu clientCount, moramo je promeniti na dva mesta. Ovo moramo imati na umu, jer moe 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. U sledeoj tabeli moe se vidjeti da ovakva optimizacija daje znaajno ubrzanje od 20% u 2 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. Tabela 8.Razbijanje petlje (Unswitching) Jezik Standardno vreme Vreme optimizovanog koda Uteda u vremenu C++ 2.81 2.27 19% Java 3.97 3.12 21% Visual Basic 2.78 2.77 <1%
7.4.Spajanje petlji (jamming)
Spajanje petlji (jamming) je tehnika u kojoj se spajaju tj. kombinuju dve petlje koje operiu nad istim skupom podataka, odnosno elemenata. Evo primera koji je pogodan za spajanje petlji u Visual Basic-u: For i = 0 t o empl oyeeCount - 1 empl oyeeName( i ) = " " Next
. . .
For i = 0 t o empl oyeeCount - 1 empl oyeeEar ni ngs( i ) = 0 - 19 - Next Kada spajamo petlje, traimo kod u dve petlje koji moemo da iskombinujemo u jednu petlju. Obino, to znai da brojai petlji moraju da budu isti. U ovome primeru, obe petlje kreu od 0 i vrte se do employeeCount 1, tako da ih moemo spojiti u jednu petlju:
For i = 0 t o empl oyeeCount - 1 empl oyeeName( i ) = " " empl oyeeEar ni ngs( i ) = 0 Next
Tabela 9.Utede nastale spajanjem petlji Jezik Standardno vreme Vreme optimizovanog koda Uteda u vremenu C++ 3.68 2.65 28% Visual Basic 3.75 3.56 4%
Spajanje petlji nosi sa sobom najmanje 2 znaajne opasnosti. Prvo, indeksi za spojene petlje bi tokom vremena mogli da se promene, tako da bi te osnovne petlje postale nekompatibilne. Drugo, moda neemo moi tako lako da spojimo petlje koje na prvi pogled djeluju kao odlini primeri za spajanje. Pre nego to spojimo petlje, budimo sigurni da e delovi koda koji se spajaju biti u istom rasporedu u odnosu na ostali kod.
7.5.Razvijanje petlje (unrolling)
Cilj razvijanja petlje jeste da se smanji koliina odravanja petlje. Iako je potpuno razvijanje petlje brzo reenje koje pri tome i radi dobro kada baratamo malim brojem elemenata, ono nije praktino kada se bavimo velikim brojem elemenata koje treba obraditi ili kada unapred ne znamo koliko emo elemenata imati. Pogledajmo primer petlje napisane u jeziku Java koji je mogue razviti (unroll): i = 0; <- - 1
whi l e ( i < count ) { a[ i ] = i ; i = i + 1; } - 20 - Ovakvu petlju bismo verovatno najee realizovali koritenjem for naredbe, ali da bi je optimizovali, potrebno je da je konvertujemo u while petlju.
8.TRANSFORMACIJA 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. Ovo su neke od tehnika koje mogu da pomognu u optimizaciji aplikacija - Koristiti cele brojeve umesto brojeve sa pokretnim zarezom, Sabiranje, kao i mnoenje, celih brojeva je daleko bre nego kada se radi sa brojevima sa pokretnim zarezom. Ako recimo imamo situaciju da je broja petlje realan broj, njegova promena u celi broj, moe znaajno ubrzati petlju. U sledeem primeru u Visual Basic-u moe se vidjeti da se kao broja koristi realan broj: Di mx As Si ngl e
For x = 0 t o 99 a( x ) = 0 Next - Koristiti najmanji mogui broj dimenzija niza, - Minimizirati referenciranje niza, - Koristiti dodatne indekse. Koritenje dodatnih indeksa podrazumeva dodavanje povezanih podataka koji ine da pristupanje odreenoj strukturi bude efikasnije. Dodavanje podataka (indeksa) moe da se izvri na glavnoj strukturi ili se moe izvriti na paralelnoj strukturi: Index za duinu stringa (string-length index): Jedan od primera koritenja dodatnih indeksa se moe nai kod razliitih strategija za uvanje stringova: U C-u, stringovi se zavravaju bajtom koji je postavljen na vrednost 0.U formatu stringova kod Visual Basic-a, skriveni bajt se nalazi na poetku stringa i on govori koliko je string dugaak. Kako bi utvrdili duinu stringa u C-u, program mora da krene od poetka stringa i da broji svaki bajt dok ne stigne do bajta koji je setovan na 0. - 21 - Nezavisna, paralelna struktura indeksa: Ponekad je efikasnije da manipuliemo indeksom nekih podataka nego samim podacima. Ako su elementi strukture podataka veliki ili zahtevni za premetanje, sortiranjei pretraga referenci indeksa je daleko bra nego sortiranje i pretraga samih podataka. Ako su sami elementi (podaci) u strukturi preveliki, moemo da kreiramo dodatnu strukturu koja se sastoji od lakih podataka koja sadri kljune vrednosti i pokazivae (pointere) na osnovnu strukturu za detaljne informacije. [2] - Koristiti keiranje (caching). Keiranje predstavlja tehniku uvanja odreenog seta vrednosti na takav nain da se zapisima koji se najee koriste lake i bre pristupa nego manje koritenim zapisima. Ako program koristi nasumine podatke sa tvrdog diska, rutina bi mogla da keira (sauva) zapise koji se najee koriste. Kada rutina primi zahtjev za odreenim zapisom, ona proveri da li ima sauvanu tu vrednost u keu. Ako je tu, zapis se uzima direktno odatle, a to je obino iz radne memorije, a ne iz osnovne strukture gde se nalaze svi zapisi (obino na tvrdom disku). Pored keiranja podataka na disku, ono se moe primeniti i u mnogim drugim oblastima. Keiranje je jo veoma pogodno za uvanje rezultata vremenski zahtjeva izraunavanja, posebno ako su parametri za kalkulaciju jednostavni. U sledeem primeru moemo vidjeti kako bi mogla da izgleda standardna implementacija ove rutine u jeziku Java: doubl e Hypot enuse( doubl e si deA, doubl e si deB) { r et ur n Mat h. sqr t ( ( si deA * si deA ) + ( si deB * si deB ) ) ; } Ako znamo da se vrednosti kateta esto ponavljaju, moemo da keiramo rezultat izraunavanja na sledei nain: pr i vat e doubl e cachedHypot enuse = 0; pr i vat e doubl e cachedSi deA = 0; pr i vat e doubl e cachedSi deB = 0;
publ i c doubl e Hypot enuse( doubl e si deA, doubl e si deB) {
/ / pr over ava da l i se t r ougao vec nal azi u kesu i f ( ( si deA == cachedSi deA ) && ( si deB == cachedSi deB ) ) { r et ur n cachedHypot enuse; }
/ / i zr acunava novu hi pot enuzu i kesi r a j e cachedHypot enuse = Mat h. sqr t ( ( si deA * si deA ) + ( si deB * si deB ) ) ; cachedSi deA = si deA; cachedSi deB = si deB;
- 22 - r et ur n cachedHypot enuse; } Druga verzija rutine je komplikovanija nego prva i zauzima vie prostora i memorije, tako da brzina treba da bude izrazit prioritet da bi se opravdala ovakva implementacija. Ovo su neke od razlika u brzini: Tabela 10.Brzina normalne i keirane rutine
Jezik Standardno vreme Vreme optimizovanog koda Uteda u vremenu Odnos performansi C++ 4.06 1.05 74% 4:1 Java 2.54 1.40 45% 2:1 Visual Basic 24.0 12.9 47% 2:1
Uspeh keiranja zavisi od relativne cene pristupanja keiranom elementu, kreiranja nekeiranog elementa i snimanja novog elementa u ke. Uspeh dalje znaajno zavisi i od toga koliko esto se keiranje informacija potrauje. U nekim sluajevima, uspenost moe zavisiti i od keiranja koje se izvodi u hardveru. Generalno, to je skuplje generisanje novog elementa i to se vie puta ista informacija potrauje, ke je vredniji,tj.daje bolje rezultate. Dalje, to je laki pristup elementima kea i snimanje elementa u ke, ke daje veu brzinu. Kao i sa drugim optimizacionim tehnikama, keiranje poveava kompleksnost koda, kao i mogunost greaka u kodu (koje se tee detektuju).
8.1.Izrazi
Veliki dio programa se odvija unutar matematikih ili logikih izraza. to su izrazi komplikovaniji, to vie resursa i vremena zauzimaju. Ovo su neki od naina: Iskoristiti algebarske identitete, Moemo da iskoristimo algebarske identitete kako bi zahtevnije operacije zamenili manje zahtevnim. Na primer, sledei izrazi su ekvivalentni: not a and not b not ( a or b) - 23 - Ako izaberemo da koristimo drugi izraz umesto prvoga utediemo jednu not operaciju. Iako je uteda od jedne not operacije zanemarljiva, generalni princip je veoma moan. Koristiti redukciju snage. Redukcija snage predstavlja zamenu skupe operacije jeftinijom. Evo nekih od moguih zamena u tom sluaju: -zameniti mnoenje sabiranjem, -zameniti stepenovanje mnoenjem, -zameniti brojeve u pokretnom zarezu brojevima fiksne irine ili celim brojevima, -zameniti brojeve u pokretnom zarezom duple preciznosti brojevima u pokretnom zarezu jednostruke preciznosti, -zameniti mnoenja i deljenja celih brojeva sa drugim adekvatnim operacijama sa bitovima. Optimizacija programa putem preraunavanja (vrednosti, izraza...) moe da ima nekoliko oblika: - Izraunavanje vrednosti pre nego to program pone da se izvrava i vezivanje vrednosti sa konstantom koja se dodeljuje za vreme kompajliranja; - Izraunavanje rezultata pre nego to program pone da se izvrava i runo dodjeljivanje promenljivima za vreme izvravanja programa; - Izraunavanje rezultata pre nego to program pone da se izvrava i snimanje istih u fajl koji se uitava za vreme izvravanja programa; - Izraunavanje rezultata jedanput, na poetku programa, i njihovo referenciranje svaki put kada su potrebni; - Izraunavanje to je mogue vise vrednosti pre poetka petlje; - Izraunavanje rezultata prvi put kada su potrebni i njihovo uvanje tako da se mogu pozvati kada budu ponovo neophodni. [4]
- 24 - 8.2.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 mjestima. Takve rutine ine da je program lako optimizovati zbog toga to refaktorisanje koda u jednoj rutini donosi poboljanje performansi na svim mjestima gdje 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. [4]
- 25 - ZAKLJUAK
Moemo oekivati da su se atributi performansi znaajnije promjenili u poslednjih 10-15 godina. Na neki nain i jesu. Kompjuteri su dramatino bri nego u to vreme, 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 izvrimo oko 100 miliona testova da bi se dobio merljiv rezultat, moramo da se zapitamo da li e iko ikada primjetiti uticaj optimizacija u realnoj eksploataciji programa. Raunari (hardver) su postali tako moni da su brojne optimizacije performansi, za mnoge iroko koritene primene, postale irelevantne. Tj. problemi vezani za performanse su ostali prilino isti. Programerima koji piu desktop aplikacije ove informacije moda nisu ni potrebne, ali onima koji piu za embedded sisteme, sisteme u realnom vremenu i druge sisteme sa striktnim zahtjevima vezano za brzinu i memoriju mogu biti od velike koristi. Optimizacija koda uvek podrazumeva ustupke izmeu kompleksnosti, itljivosti, jednostavnosti i lakoe 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. Moemo rei da je insistiranje na merljivim poboljanjima dobar put da se odupremo iskuenjima prerane optimizacije, kao i siguran put da se nametne pisanje istog standardnog koda. Ako je optimizacija dovoljno vana da se prizove softver za profilisanje softvera, onda je verovatno i dovoljno vana da bi se i dozvolila optimizacija, naravno sve dok radi ispravno. Ali, ako optimizacija nije dovoljno vana da bi prizvala softver za profilisanje, onda optimizacija svakako nije dovoljno bitna da bi se zbog nje degradirali itljivost, lakoa odravanja i druge karakteristike koda.
- 26 - LITERATURA
[1] W. Addison, Inenjering softvera (peto izdanje), 1995 godina. [2] D. Spinelis, Kod za itanje, 2003 godina. [3] D. Budgen, Software design. 1994. godina. [4] S. McConnell, Code Complete, Second Edition, 2004 [5] http://www.maturskiradovi.net/