diplomski rad strategije i tehnike optimizacije programskog koda

70
SVEUČILIŠTE U DUBROVNIKU ODJEL ZA ELEKTROTEHNIKU I RAČUNARSTVO STUDIJ POSLOVNOG RAČUNARSTVA DIPLOMSKI RAD STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA Dubrovnik, prosinac 2011.

Transcript of diplomski rad strategije i tehnike optimizacije programskog koda

Page 1: diplomski rad strategije i tehnike optimizacije programskog koda

SVEUČILIŠTE U DUBROVNIKU

ODJEL ZA ELEKTROTEHNIKU I RAČUNARSTVO STUDIJ POSLOVNOG RAČUNARSTVA

DIPLOMSKI RAD STRATEGIJE I TEHNIKE OPTIMIZACIJE

PROGRAMSKOG KODA

Dubrovnik, prosinac 2011.

Page 2: diplomski rad strategije i tehnike optimizacije programskog koda

SVEUČILIŠTE U DUBROVNIKU

ODJEL ZA ELEKTROTEHNIKU I RAČUNARSTVO STUDIJ POSLOVNOG RAČUNARSTVA

DIPLOMSKI RAD STRATEGIJE I TEHNIKE OPTIMIZACIJE

PROGRAMSKOG KODA

Mentor: Diplomant:

doc.dr.sc. Mario Miličević Robert Primorac Komentor:

mr.sc. Krunoslav Žubrinić

Dubrovnik, prosinac 2011.

Page 3: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

I

SADRŽAJ 

1  Uvod .................................................................................................................................... 1 2  Strategije optimizacije ........................................................................................................ 3 

2.1  Performanse ................................................................................................................ 4 2.2  Performanse i optimiziranje koda .............................................................................. 5 

2.2.1  Zahtjevi aplikacije .................................................................................................. 5 2.2.2  Dizajn aplikacije ..................................................................................................... 5 2.2.3  Dizajn klasa ............................................................................................................ 7 2.2.4  Interakcije s operacijskim sustavom ...................................................................... 7 2.2.5  Prevođenje programskog koda ............................................................................... 7 2.2.6  Strojna oprema ....................................................................................................... 7 

3  Optimizacija programskog koda ....................................................................................... 10 3.1  Paretov princip ......................................................................................................... 10 3.2  Uobičajene neistine .................................................................................................. 11 

3.2.1  Smanjivanje linija koda u jeziku više razine će poboljšati njegovo izvođenje .... 11 3.2.2  Neke operacije će vjerojatno biti brže od drugih ................................................. 12 3.2.3  Treba optimizirati dok se programira ................................................................... 13 3.2.4  Brzina programa je uvijek jednako važna kao i njegova ispravnost .................... 14 

3.3  Kada optimizirati kod? ............................................................................................. 14 3.4  Optimizacije prevoditelja ......................................................................................... 14 3.5  Uska grla .................................................................................................................. 15 3.6  Najčešći izvori neefikasnosti .................................................................................... 15 

3.6.1  Ulazno-izlazne operacije ...................................................................................... 15 3.6.2  Straničenje ............................................................................................................ 17 3.6.3  Pozivi prema sistemu ........................................................................................... 17 3.6.4  Greške u programskom kodu ............................................................................... 18 

3.7  Mjerenje ................................................................................................................... 18 3.8  Iteracije ..................................................................................................................... 19 3.9  Proces optimizacije koda .......................................................................................... 19 

3.9.1  Definiranje osnova ............................................................................................... 20 3.9.2  Prikupljanje podataka ........................................................................................... 20 3.9.3  Analiza rezultata ................................................................................................... 20 3.9.4  Konfiguriranje ...................................................................................................... 21 3.9.5  Testiranje i mjerenje ............................................................................................. 22 

4  Tehnike optimizacije ......................................................................................................... 23 4.1  Logika ....................................................................................................................... 23 

Page 4: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

II

4.1.1  Zaustavljanje grananja .......................................................................................... 23 4.1.2  Lijena evaluacija .................................................................................................. 25 4.1.3  Trenutna lokacija .................................................................................................. 25 

4.2  Optimizacija nizova znakova ................................................................................... 26 4.2.1  String.concat ......................................................................................................... 27 4.2.2  StringBuilder klasa ............................................................................................... 28 4.2.3  Kada koristiti StringBuilder klasu ........................................................................ 29 4.2.4  Pretvaranje integera u string ................................................................................. 31 4.2.5  Petlje i pojedinačni znakovi ................................................................................. 34 

4.3  Optimizacija petlji .................................................................................................... 35 4.3.1  For petlja .............................................................................................................. 35 4.3.2  Foreach petlja ...................................................................................................... 36 4.3.3  Razlika u brzini između for i foreach ................................................................... 36 4.3.4  Dekrementacija u for petlji ................................................................................... 37 4.3.5  Fuzija petlji ........................................................................................................... 37 4.3.6  Odmotavanje petlji ............................................................................................... 38 4.3.7  Unswitching .......................................................................................................... 39 4.3.8  Manje posla unutar petlji ...................................................................................... 41 

4.4  Polja .......................................................................................................................... 41 4.4.1  Polja umjesto kolekcija ........................................................................................ 42 4.4.2  Čvrsto definirana polja ......................................................................................... 42 4.4.3  Krnja polja ............................................................................................................ 43 4.4.4  Spljošćivanje polja ............................................................................................... 43 

4.5  Tipovi podataka ........................................................................................................ 44 4.5.1  Dodatni podaci ..................................................................................................... 45 4.5.2  Keširanje ............................................................................................................... 45 

4.6  Izrazi ......................................................................................................................... 46 4.6.1  Algebarski izrazi .................................................................................................. 46 4.6.2  Smanjivanje snage ................................................................................................ 47 4.6.3  Inicijalizacija pri prevođenju ................................................................................ 47 4.6.4  Eliminiranje podizraza ......................................................................................... 49 

4.7  Metode ...................................................................................................................... 49 4.7.1  Ručno kreiranje inline metoda ............................................................................. 50 4.7.2  Statične metode .................................................................................................... 51 4.7.3  Manji broj parametara u metodama ..................................................................... 52 4.7.4  Performanse parametara metode i registri ............................................................ 53 4.7.5  Preopterećenje nativnih metoda ........................................................................... 54 

Page 5: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

III

4.8  Dretve ....................................................................................................................... 55 4.8.1  Smanjiti stvaranje dretvi ....................................................................................... 55 4.8.2  ThreadPool klasa .................................................................................................. 56 4.8.3  Paralelni poslovi ................................................................................................... 57 

4.9  Obrada iznimki ......................................................................................................... 57 4.9.1  Ne koristiti iznimke da bi kontrolirali tok programa ............................................ 58 4.9.2  Koristiti validacije umjesto iznimki ..................................................................... 59 4.9.3  Ponovno bacanje iznimaka je skupo .................................................................... 60 

5  Zaključak ........................................................................................................................... 61 6  Literatura ........................................................................................................................... 62 7  Sažetak .............................................................................................................................. 63 8  Abstract ............................................................................................................................. 64 9  Prilozi ................................................................................................................................ 65 

9.1  Popis slika ................................................................................................................ 65 

Page 6: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

1

1 UVOD 

Osnovni motiv pri odabiru ove teme diplomskog rada bio je praktičan problem koji mi se

pojavio na poslu. Kao programer u C# jeziku nikad nisam puno razmišljao o optimizaciji

koda sve do nedavno kada sam trebao ubrzati operaciju s 15 sekundi na 5. Bio sam malo

pogubljen jer nisam znao kako i od kuda početi. Ubrzo sam se našao na Google-u s

ključnom riječi „optimization C#“ i dobio hrpu podataka koji su bili jako interesantni. Ono

što me zadivilo bila je činjenica da to nije automatski posao kod kojeg se oslonite na

postojeći obrazac pa znate kako će izgledati ishod posla.

Postoje mnoge teorije optimizacije tako da je to polje jako široko. Ovaj diplomski rad se

dotiče strategija i tehnika optimizacije. Na većem broju primjera objasniti ću razloge zbog

kojih treba koristiti odgovarajuću tehniku te zbog čega će rezultat njezinog izvođenja biti

brži. Dok strategije optimizacije opisuju općenita područja i pojmove vezane uz

optimizaciju, tehnike optimizacije se odnose na konkretnu realizaciju.

Ako ste programer, molim vas da ne shvatite navedene primjere doslovno i odmah počnete

mijenjati vaš programski kod, jer kao što je William Allan Wulf, pionir optimizacije

prevoditelja jednom napisao „Više računalnih grijeha je počinjeno u ime efikasnosti (bez

njezinog nužnog dostizanja) nego u ime ičega drugoga – uključujući i gluposti [6].“

No isto bih tako želio da ipak razmislite malo o navedenim primjerima jer kao i meni, tako

će vjerojatno i vama pomoći.

Struktura ovog rada je slijedeća: Nakon uvodnog dijela, u drugom poglavlju opisana je

problematika optimizacije s naglaskom na različita tumačenja pojma performanse i

različite pristupe poboljšanju performansi. Metode optimizacije su opisane u trećem

poglavlju. Taj dio je vrlo važan jer pokazuje korake koje treba poduzeti prije optimizacije

koda da bi se izbjegao negativan utjecaj optimiziranja na ispravnost i čitljivost koda. U tom

dijelu se dotičem mjerenja kao jedinog sredstva prepoznavanja poboljšanja performansi.

Pojašnjavaju se uobičajeni izvori uskih grla te kako ih izbjeći. Četvrto poglavlje posvećeno

je konkretnim tehnikama poboljšanja performansi programskog koda na najnižoj razini. U

njemu su navedeni detaljno primjeri programskog koda u C# programskom jeziku. U tom

poglavlju neće se naći primjeri optimizacije na višoj razini, poput poboljšanja algoritama

ili načina za optimizaciju pretrage tablice. Naći će se primjeri optimizacije algebarskih

izraza, operacija s nizovima podataka, metode, i sl što će možda u konačnici ubrzati vašu

Page 7: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

2

pretragu tablice. Smisao ove cjeline je spuštanje na što je moguće nižu razinu

programiranja te optimiziranje toga dijela, reflektirajući promjenu prema gore. Obrađene

su tehnike optimizacije koje se odnose na najučestalije probleme s kojima se programer

svakodnevno susreće. Kao ilustracija većine opisanih tehnika napisanu su konkretni

primjeri neoptimiziranog i optimiziranog koda u C# programskom jeziku, te su izmjereni i

prikazani rezultati ostvareni korištenjem odgovarajuće optimizacijske tehnike. U petom

zaključnom poglavlju dan je kratak osvrt na napisani diplomski rad.

Testovi su se izvršavali na prijenosnom računalu marke Acer, a model je Aspire 5920.

Najvažnije komponente koje bih naveo su dvojezgreni procesor Intel Core 2 Duo T7300 na

1.5 GHz, Mobile Intel PM965 Express Chipset, 2GB DDR2 memorije na 667MHz te čvrsti

disk veličine 160GB. Operacijski sustav koji se nalazi na prijenosniku je Microsoft

Windows 7. Programsko okruženje je Microsoft Visual Studio 2010 s .NET Framework-om

4.0.

Page 8: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

3

2 STRATEGIJE OPTIMIZACIJE 

Koje su to strategije optimizacije? Što je to optimizacija? Ova pitanja se povlače već dulje

vremena u svijetu programiranja i dizajniranja aplikacija tako da se može reći da su to

povijesna pitanja. Šezdesetih godina prošloga stoljeća, računalni resursi su bili mali te je

program koji je koristio malo tih resursa, a uspješno obavljao svoju zadaću bio dobar

program. U to vrijeme efikasnost programa je bila izuzetno važna. Desetljeće nakon toga,

računala su postala znatno snažnija što je natjeralo razvojnike (engl. developers) aplikacija

da stanu i promisle da li im je efikasnost aplikacije stvarno toliko važna? Možda je važnije

da programski kod bude čitljiviji i bolje strukturiran? Zbog razmišljanja o takvim pitanjima

razvojnici su se fokusirali na fino strukturiranje aplikacija, dok su manje pažnje

posvećivali optimizaciji programskog koda. S evolucijom razvoja mikroračunala u

osamdesetim godinama dvadesetog stoljeća, optimizacija je opet postala važna, da bi

devedesetih godina opet pala u zaborav. Dvije tisućite se vratila zahvaljujući revoluciji

ugrađenih (engl. embedded) aplikacija koje se koriste na ručnim računalima i mobitelima.

Na slici 1 je vidljiv ciklus razvoja optimizacije računalnih aplikacija.

Slika 1 Ciklus odnosa optimizacije i snage računala

Razmišljanje o strategijama optimizacije može se sagledati odgovarajući na nekoliko

važnih pitanja o željenim performansama aplikacije:

• Što su to performanse?

Optimizacija je potrebna

Snažnija računala

Optimizacija nije potrebna pa se gleda čitljivost i struktura

Optimizacija je potrebna

Snažnija računala

Optimizacija nije potrebna pa se gleda čitljivost i struktura

Page 9: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

4

• Koliko su nam performanse važne?

• Kako ostvariti željene performanse?

2.1 Performanse 

Optimizacija koda je jedan od načina dobivanja na performansama. Pored same

optimizacije, postoje i drugi načini za postizanje boljih performansi, poput nabavke jačih

računala. To je rezultat činjenice što su korisnici zainteresirani za karakteristike i opcije

programa poput brzine izvođenja, a ne za kvalitetan programski kod. Performanse su im

važne samo onda kada imaju izravnu potreba za takvim kodom. Slijedeći primjer će

ilustrirati takav slučaj.

Imamo aplikaciju koja komprimira podatke. Ta aplikacija može komprimirati jednu

datoteku ili više datoteka. Komprimiranje više datoteka se vrši na način da se odabere

datoteka i da se komprimira, te da se svaka sljedeća datoteka "zalijepi" na prethodnu

datoteku. Na taj način dobivamo arhivu komprimiranih datoteka. Problem za korisnika je

taj da ako želi komprimirati 20 datoteka, mora 20 puta kliknuti na datoteku i reći da ju želi

komprimirati i dodati u postojeću arhivu. Da primjer bolje dočara odnos performansi i

brzine korištenja, reći ćemo da ova aplikacija može komprimirati neku datoteku kapaciteta

10 MB za 2 sekunde.

Druga aplikacija komprimira datoteku od 10 MB za 3 sekunde. Možemo reći da je ova

aplikacija po svojim performansama preko 30% sporija od prethodne. No ono što ova

aplikacija dopušta je odabiranje skupine datoteka i davanje komande da se od svih

odabranih datoteka napravi jedna arhivu. Korisnik je pošteđen mnoštva klikova mišem i

odabiranja kojoj arhivi će se dodati novoizabrana datoteka.

Što dobivamo u konačnici?

Dobivamo aplikaciju koja ima slabije performanse od prethodne kad govorimo o

komprimiranju, no zbog brzine korištenja su joj konačne performanse sa stajališta

upotrebljivosti za krajnjeg korisnika bolje.

Performanse su labavo povezane s brzinom izvođenja programskog koda do te mjere da,

ako se brzina izvođenja programskog koda pokušava previše optimizirati, to može naštetiti

drugim karakteristikama programa. Kada se programski kod ubrzava, treba biti svjestan da

Page 10: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

5

će pritom možda biti potrebno žrtvovati druge karakteristike što u konačnici može naštetiti

ukupnim performansama programa. Zbog toga je pri optimizaciji potrebno odrediti što je

prioritet i ravnati se prema tome.

2.2 Performanse i optimiziranje koda 

Jednom kada se izabere efikasnost kao prioritet, treba razmotriti nekoliko opcija prije nego

se krene s poboljšanjem brzine izvođenja ili smanjenja duljine programskog koda.

Efikasnost se mora sagledat iz nekoliko različitih točaka:

• Zahtjevi aplikacije;

• Dizajn aplikacije;

• Dizajn klasa;

• Interakcije sa operacijskim sustavom;

• Prevođenje koda;

• Strojna podrška;

• Optimiziranje koda.

2.2.1 Zahtjevi aplikacije 

Performanse se jako često ističu kao zahtjev od strane korisnika, no korisnik ponekad ne

zna jasno izraziti što je to u programu što bi s njegovog stajališta trebalo poboljšati.

Nekada je to stvarno brzina izvođenja, ali to može biti i neka druga osobina poput

ergonomije korisničkog sučelja. Zbog toga prije nego se uloži vrijeme u rješavanje

problema performansi treba biti siguran da se rješava stvaran problem koji se treba

rješavati[1].

2.2.2 Dizajn aplikacije 

Ovaj nivo sadržava važne temelje dizajna aplikacije u pogledu dekompozicije aplikacije na

sastavne dijelove. U slučaju objektno orijentiranog razvoja to su klase i skupine klasa.

Zbog pogrešnih odluka donesenih pri dekompoziciji i modeliranju, ponekad je jako teško

pisati programski kod i posebno poboljšavati performanse aplikacije.

Razmotrite primjer nekog sustava koji interpretira neku pojavu iz svijeta koji nas okružuje.

Na primjer, sustav koji računa opterećenost nekog objekta na N njegovih točaka. Ono što

Page 11: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

6

se zahtjeva je vrlo brz izračun opterećenja i prezentiranje informacija korisniku. Za svaku

točku se treba računati opterećenje što zahtjeva preračun između različitih mjernih jedinica

koje se koriste za prikaz pojava u stvarnom svijetu u "inženjerski prikaz" razumljiv

korisniku sustava. Izračunati podaci moraju biti što točniji tako da će sa tehničke strane

izračun biti jako kompliciran. U konačnici, brojeve treba prikazati u decimalnom

brojevnom sustavu. Krivi smjer optimizacije bi bio ubrzanje matematičkih izračuna, uz

zanemarivanje preciznosti kako bi korisnici što prije dobili prikazane rezultate.

Ako je potrebno da program bude brz onda se on od samog početka treba dizajnirati s tim

na umu. Znači, od samog početka treba dizajnirati aplikaciju tako da bude orijentirana

boljim performansama a potom bi trebalo definirati i dizajnirati ostale module i klase. U

tom slučaju se ponekad moraju donijeti i teške odluke poput izbora nekog specifičnog

razvojnog alata ili programskog jezika s kojim razvojni tim možda nije dobro upoznat, ali

čije korištenje će omogućiti ostvarenje željenih performansi.

Dizajniranje aplikacije pomaže poboljšanju performansi na nekoliko načina:

• Ako se kao pojedinačni ciljevi postave očekivane performanse pojedinačnih

modula onda konačne performanse sustava postaju predvidljive. Ako svaki modul

dostigne svoje ciljeve, onda će i cijela aplikacija vrlo vjerojatno dostići svoje

ciljeve. Na ovaj način se mogu identificirati moduli koji ne dostižu ciljeve te ih se

može lakše redizajnirati bez promjene sustava kao cjeline.

• Samo razmišljanje o performansama tijekom početne faze dizajniranja, te

postavljanje konkretnih ciljeva u toj fazi eksplicitno povećava šansu da će se taj cilj

i dostići. Programeri će se lakše baviti rješavanjem problema kada imaju zadane

konkretne ciljeve, naročito ako su ti ciljevi numerički (npr. ova obrada mora

završiti u prosjeku za 3 sekunde; najgori slučaj koji se smije dogoditi je 5 sekundi, i

ne bi se smio javljati u više od 10% slučajeva). Što je cilj eksplicitniji lakše je raditi

na njegovom ostvarenju.

• Mogu se postaviti ciljevi koji nužno ne poboljšavaju efikasnost ali ju promoviraju

na duge staze. Efikasnost najbolje dolazi do izražaja u nekim drugim problemima.

Na primjer, ako se dostigne veliki stupanj modularnosti sustava na način da se

sustav lako može proširiti ili da se neki moduli lako mogu zamijeniti onda je

dostignut visoki stupanj efikasnosti, ali bez nekih eksplicitnih ciljeva. Ovo će doći

do izražaja ako se identificira modul koji sporo radi. Taj modul se lako može

Page 12: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

7

zamijeniti s modulom koji radi brže te je na taj način dostignuta željena razina

efikasnost.

2.2.3 Dizajn klasa 

Dizajniranje klasa predstavlja priliku za dizajniranje samih performansi. Jedna od ključnih

performansi na ovom nivou su izbor tipova podataka i algoritama koji često znaju utjecati

na korištenje memorije tijekom obrade a samim time i na brzinu izvršavanja programa. U

slučaju kritičnih obrada kod kojih su performanse izvođenja ključne, tijekom ova faze

razvojnom timu se mogu zadati konkretni algoritmi koje moraju koristiti kako bi se željene

performanse postigle.

2.2.4 Interakcije s operacijskim sustavom 

Svaki program se izvodi u okruženju operacijskog sustava i tijekom rada vrši interakciju s

njim. Ako program koristi vanjske datoteke, dinamičku memoriju ili vanjske uređaje, ta

interakcija je znatno intenzivnija

Ako su performanse izvođenja odgovarajućih operacija slabe, potrebno je provjeriti da su

uzrok toga funkcije operacijskog sustava koje su spore ili neodgovarajuće za određenu

vrstu obrade. Uzrok greške može biti i samo razvojno okruženje odnosno problemi pri

prevođenju programskog koda u izvršnu verziju.

2.2.5 Prevođenje programskog koda 

Dobar prevoditelj prevodi programski jezik u dobro optimizirani strojni kod, tako da izbor

dobrog prevoditelja rješava veliki dio optimizacije. Slične probleme imaju interpreterski

jezici koji tijekom izvođenja programa dinamički "u hodu" prevode programski kod. Kod

njih se lošije performanse ponekad teže zapažaju u slučaju kada ovise o trenutnom stanju

okruženja u kojem se program izvodi.

2.2.6 Strojna oprema 

Nekada je najjednostavniji način poboljšanja performansi kupnja novog računala. Ako se

radi aplikacija koja je namijenjena tisućama korisnika onda česta promjena strojne opreme

nije najbolja opcija, ali ako se radi neku specifična aplikaciju za jednog ili nekoliko

poznatih korisnika onda ova opcija zna biti najjeftinija jer se štedi na trošku rada razvojnog

tima tijekom faze poboljšanja performansi. Pored toga, ušteda se javlja i zbog toga jer se u

Page 13: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

8

budućem održavanju neće javljati problemi i greške koji su nastali isključivo kao

posljedica poboljšanja performansi. Pored toga, i drugi programi koji će se izvoditi na istoj

opremi vjerojatno imati će dobiti bolje performanse, bez ikakve intervencije u programski

kod. Ovakav način poboljšanja performansi danas je dosta često prisutan u slučajevima

kada je to opravdano, ali i onda kada to nije opravdana. Korisnici se često povode za

izjavama da npr. treba svake dvije godine zamijeniti računala kako bi sustav normalno

funkcionirao.

U nekim slučajevima dodavanje i pojačavanje strojne opreme neće pomoći poboljšanju

performansi aplikacije. Tipičan primjer takvog slučaja je ako se u obradi koriste algoritmi

velike složenosti. Npr. ako je algoritam eksponencijalne i više složenosti (npr. O(2n),

O(3n),…) pojačanje opreme neće znatnije poboljšati brzinu izvođenja već je potrebno

napisati novi algoritam niže razine složenosti.

Kada se govori o unaprjeđenju performansi poboljšanjem strojne opreme, postoje dvije

osnove tehnike kojima se povećavaju performanse, a to su skaliranje prema gore i

skaliranje prema van. Skaliranje je mogućnost aplikacije da zadrži sposobnost održavanja

performansi uz povećanje rada same aplikacije. Uobičajeni ciljevi performansi su

zadržavanje vremena odaziva i prohodnosti.

• Skaliranje prema gore znači poboljšanje trenutne strojne opreme [4]. Tu

govorimo o novom procesoru, memoriji disku ili mrežnom upravljaču. Kandidati za

zamjenu su one komponente o kojima performanse najviše ovise. Ponekad se

zamjenjuje čitav poslužitelj. Takva tehnika je dobra jer je relativno jeftina, a ne

donosi nikakve nove troškove održavanja. Ono što ne valja je da u jednom

trenutku, dodavanje nove jače strojne opreme neće pomoći performansama jer i

sama aplikacija mora svojom funkcionalnošću pratiti pojačanje strojne opreme.

• Skaliranje prema van znači dodavanje više poslužitelja trenutnoj skupini

poslužitelja i stvaranje tzv. farme poslužitelja [4]. Time se dobiva rasterećenje

trenutnog sustava odnosno razvodnjavanje posla na više paralelnih obrada. Ovakav

način se može koristiti samo u slučaju ako operacijski sustav i drugi korišteni

resursi podržavaju takav način rada

Skaliranje ima određene prednosti i nedostatke o kojima treba voditi računa:

• Skaliranje prema gore je najbolje za aplikacije koje paralelno izvršavaju poslove.

Page 14: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

9

• Skaliranje prema van je najbolje za poslove kojima se tijekom korištenja povećava

obujam korištenja (npr. farme web poslužitelja).

• Ako aplikacija obavlja poslove koji se simultano izvršavaju i koji su neovisni

jedan o drugome, te se aplikacija pokreće na poslužitelju s jednim procesorom,

onda se poslovi trebaju izvršavati asinkrono tako da jedna obrada ne mora čekati

završetak druge. Asinkrono procesiranje je puno bolje za poslove koji su usko

vezani uz veći broj ulazno-izlaznih operacija. U ovom slučaju najbolje je skalirati

prema gore i dodati još procesora.

• Ograničenja koja su postavljena operacijskim sustavima mogu biti prepreka

skaliranju prema gore. Na primjer u slučaju ako operacijski sustav podržava samo

dva procesora i 4GB memorije, onda nema smisla dodavati više od toga.

• Skaliranje prema van donosi nove troškove u vidu održavanja.

Page 15: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

10

3 OPTIMIZACIJA PROGRAMSKOG KODA 

Optimizacija programskog koda je praksa modificiranja ispravnog programskog koda u

svrhu efikasnijeg izvršavanja istog [4]. Optimiziranje se odnosi na male promjene koje

zahvaćaju jednu klasu, funkciju ili kao što je vrlo često slučaj, samo par linija koda.

Optimiziranje se ne odnosi na velike promjene čak i ako te promjene donose poboljšanje.

Taj postupak nije najefektivniji način za poboljšanje performansi. Dobra arhitektura, dizajn

klasa ili pažljivo izabrani algoritmi će napraviti veće promjene od samog optimiziranja. To

najlakši način dobivanja na performansama jer je nabavka nove i bolje strojne opreme ili

boljeg prevoditelja puno lakša od optimiziranja. Potrebno je znatno više vremena da bi se

programski kod optimizirao, a takav kod je teže održavati. Postavlja se pitanje zbog čega

optimizirati programski kod?

Optimiziranje koda je primamljivo iz nekoliko razloga. Jedan od njih je neka vrsta prkosa.

Veliko je zadovoljstvo uzeti neku funkciju koja se izvršava za 20 milisekundi, optimizirati

par linija programskog koda, te na taj način smanjiti vrijeme izvršavanja na 2 milisekunde.

Također je primamljivo za programere jer pisanje dobrog i brzog koda je neka vrsta rituala

ka postajanju ozbiljnog programera. Nikoga osim drugog programera neće zanimati koliko

vam je kod brz. Bez obzira, unutar kulture programiranja, pisanje mikro efektivnog koda

dokazuje da ste ozbiljan programer.

3.1 Paretov princip 

Paretov princip je poznat i kao 80/20 pravilo koje kaže da možemo dobiti 80 rezultata uz

20 posto optimalno uloženog truda [2]. To pravilo se može primijeniti na brojna područja

uključujući programiranje, a posebno je istinito pri optimizaciji koda.

Tim ljudi koji je napravio ALGOL programski jezik, otac svih novijih programskih jezika,

je dao savjet u obliku poznate Voltairove izjave „Najbolji je neprijatelj dobrome. [5]“. Što

ta izjava znači? Ukratko, rad usmjeren isključivo prema ostvarenju savršenstva priječi

završavanje. Prvo završite posao pa se nakon toga posvetite usavršavanju. Dio koji se treba

usavršiti obično je jako mali.

Page 16: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

11

3.2 Uobičajene neistine  

Dosta stvari koje ste čuli o optimiziranju koda su neistinite. Navesti ću nekoliko

uobičajenih primjera te ću ih potkrijepiti s rezultatima koje sam izmjerio na konkretnim

primjerima programskog koda.

3.2.1 Smanjivanje  linija  koda  u  jeziku  više  razine  će  poboljšati  njegovo 

izvođenje 

Ovo je naravno neistinito. Razmotrimo sljedeće linije koda gdje ćemo pokušati

inicijalizirati polje prirodnih brojeva na dva različita načina. Prvi način je čitljiviji i ima

manje linija koda. Drugi će biti suprotno od toga. Ovu inicijalizaciju ću pokrenuti

1.000.000 puta radi lakšeg mjerenja.

Kratka

int[] polje = newint[10]; for (int i = 0; i < 10; i++) {   polje[i] = i; } 

Duga

int[] drugoPolje = newint[10]; polje[0] = 0; polje[1] = 1; polje[2] = 2; polje[3] = 3; polje[4] = 4; polje[5] = 5; polje[6] = 6; polje[7] = 7; polje[8] = 8; polje[9] = 9; 

Page 17: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

12

Slika 2 Odnos duljine programskog koda i brzine izvođenja

Slika 2 s rezultatima mjerenja prikazuje da se dulji i nečitljiviji programski kod izvršava

gotovo tri puta brže od kraćeg i čitljivijeg koda. Zaključak je da ne treba tražiti vezu

između broja linija programskog koda i brzine izvršavanja na računalu. To dokazuje da

estetika u pisanju koda ne bi trebala biti važna ako se promatra samo brzina izvršavanja

programskog koda. S druge strane, estetika programskog koda je izuzetno važna sa

stajališta programera u slučaju kada je npr. potrebno održavati postojeći programski kod

koji je napisao neki drugi programer.

3.2.2 Neke operacije će vjerojatno biti brže od drugih 

Kada govorimo o performansama ne bi trebali upotrebljavati riječ „vjerojatno“[1].

Performanse se uvijek moraju izmjeriti da bi se znalo da su neke promjene pomogle ili

odmogle aplikaciji. Rezultati mjerenja se mijenjaju svaki put kada promijenimo okruženje

poput korištenih vanjskih dll-ova, verzije frameworka ili prevoditelja, procesor, memoriju,

i sl. Rezultati koje smo dobili pri jednom mjerenju u jednom okruženju mogu vrlo lako biti

drukčiji u drugom okruženju.

Ta pojava nam daje nekoliko razloga zašto ne bismo poboljšavali performanse

optimiziranjem programskog koda. Ako želimo da program bude prenosiv, onda tehnike

koje se upotrebljavaju da bi se poboljšale performanse u jednom okruženju mogu smanjit

performanse u drugom okruženju. Ako promijenimo verziju prevoditelja, možda će ta

verzija bolje optimizirati kod nego vi ali joj je potreban standardno napisani kod. To

naravno u slučaju ručne optimizacije pada u vodu jer ste pokušali programski kod

optimizirati kod.

27

87

0

20

40

60

80

100

1 2

Duga

Kratka

Page 18: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

13

Ako ste podešavali kod onda se implicitno slažete da ćete profilirati (engl profiling)

programski kod svaki put kada promijenite verziju prevoditelja ili verziju korištenih

vanjskih programskih biblioteka.

3.2.3 Treba optimizirati dok se programira 

Postoje neke teorije koje kažu da ako pišete što manji i brži programski kod od samog

početka da će konačni proizvod biti malen i brz. Takav pristup najčešće vodi uzrečici „Od

šume se ne vidi stablo“. Zašto je to tako? Ako smo čitavo vrijeme zauzeti mikro

optimizacijama onda nećemo vidjeti globalne optimizacije koje su puno važnije od ovih

manjih. Neki uobičajeni problemi koji se javljaju kod optimiziranja od samog početka su

slijedeći:

• Gotovo je nemoguće identificirati uska grla prije nego se program počne izvoditi u

stvarnom okruženju. Programeri su jako loši u pogađanju kojih 20% koda izvršava

80% programa, tako da ako budu optimizirali dok programiraju, potrošiti će 80%

vremena optimizirajući kod koji ne treba optimizirati. Nakon toga im preostaje jako

malo vremena za optimizaciju onog koda koji je stvarno važan.

• U rijetkim slučajevima u kojima se uspije identificirati usko grlo, programeri znaju

pretjerati s optimizacijom tako da ostala uska grla koja su još neidentificirana

postaju još kritičnija. Optimizacija nakon što je sistem napravljen dopušta

identifikaciju svih uskih grla te optimalno raspoređivanje vremena na rješavanje

svih takvih mjesta.

• Fokusiranje na optimizaciju tijekom razvoja odvlači programere od krajnjeg cilja.

Programeri se upuštaju u rasprave i pisanje složenih algoritama koji krajnjem

korisniku ne znače puno. Brige poput ispravnosti rada, skrivanja informacija i

čitljivosti postaju sekundarni ciljevi, unatoč tome što su performanse stavka koja se

lakše ispravlja u kasnim fazama nego to troje.

Ukratko, osnovna loša strana prerane optimizacije je nedostatak perspektive. Ono što

najviše pati zbog toga je brzina i kvaliteta konačnog proizvoda te sami korisnici koji rade s

tom aplikacijom.

Ako se tijekom razvoja vrijeme predviđeno izradi osnovnih funkcionalnosti posveti

optimizaciji programa, onda će kao rezultat vjerojatno biti brz program koji nije nužno

Page 19: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

14

kvalitetan [1]. Ako je iz nekog specifičnog razloga potrebno optimizirati program tijekom

izrade onda treba imati zadanu perspektivu pri tome.

3.2.4 Brzina programa je uvijek jednako važna kao i njegova ispravnost 

U praksi brzina program nikada nije važnija od ispravnog programa. Na primjer za

programe koji se brinu za upravljanje zrakoplovom brzina je izuzetno važna. Ali sva

postignuta brzina neće nimalo pomoći ako u nekom svom dijelu program izračuna

pogrešne podatke. Kod takvih kritičnih primjena brzina i ispravnost su jednako i iznimno

važne. U većini svakodnevnih primjena brzina nije toliko važna koliko to ljudi na prvi

pogled smatraju. Pored toga sa sve jačom strojnom opremom, brzina postaje sva manje

važna nauštrb ispravnosti. Zbog toga se pri razvoju još u fazi dizajna rizici smanjenih

performansi moraju predvidjeti i planirati kako se ne bi došlo u situaciju da razvojni tim

optimizira nešto što nije potrebno optimizirati.

3.3 Kada optimizirati kod? 

Dobro dizajnirajte program. Napravite ga da bude ispravan. Napravite ga da bude

modularan te da se lako mogu raditi izmjene na njemu. Kada je programski kod potpun i

ispravan, tek tada provjerite performanse. Ako je program velik i trom, poboljšajte ga tako

da bude brži i manji .

Nemojte optimizirati programski kod naprečac dok ne znate što trebate optimizirati. Prvo

pronađite kritična uska grla [1]. Čest je slučaj da u jednom radnom danu možete

identificirati nekoliko uskih grla u kodu te da par zahvata na tim mjestima može drastično

ubrzati rad projekta.

3.4 Optimizacije prevoditelja 

Moderni prevoditelji su poprilično moćni kad je u pitanju optimizacija napisanog

programskog koda. Optimiziranje koje odrađuje prevoditelj odgovarajućim postavkama

može biti puno bolje od optimiziranja koda. Ako radite zamršene stvari u petljama ili

algoritmima s velikom složenošću, vaš prevoditelj će teško moći optimizirati taj kod. Kod

izbora algoritama pokušajte izabrati što je moguće jednostavniji algoritam, manje

složenosti. Kod rješavanja problema pokušajte izbjeći rekurziju bez obzira što je ona za vas

Page 20: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

15

kao programera jednostavnija, pošto ona zbog načina prijenosa podataka pri rekurzivnim

pozivima znatno opterećuje računalne resurse tijekom izvođenja programa.

3.5 Uska grla 

Usko grlo je resurs koji ograničava prohodnost i brzinu izvođenja programskog koda. U

većini slučajeva uska grla u našim aplikacijama su povezana s ključnim računalnim

resursima koje koriste aplikacije, poput procesora, radne memorije, diska ili računalne

mreže. Uska grla variraju od sloja do sloja. Kod dvoslojnih poslovnih aplikacija koje

koriste bazu podataka mogu se uočiti dvije lokacije na kojima se uska grla najčešće

javljaju:

• Web/Aplikacijski poslužitelj. Česti uzroci uskih grla uključuju neefikasno baratanje

sesijama i stanjima, konekcije, duge pozive prema web servisu ili bazi podataka te

komplicirana sučelja.

• Poslužitelj baze podataka. Česti uzroci uskih grla uključuju lošu logiku pri

dizajniranju baze, nedovoljno normalizirane tablice, loše indekse na tablicama,

premalo ili previše indeksa na tablicama i loše particionirane podatke po tablicama.

Ostali uzroci su najčešće neefikasni upiti ili procedure koje služe za komunikaciju

aplikacije s bazom podataka.

Ove dvije stavke se tiču svakog programera koji razvija takvu aplikaciju, neovisno o

programskom jeziku, platformi i bazi podataka koju koristi.

3.6 Najčešći izvori neefikasnosti 

Za vrijeme optimizacije programskog koda pronalaze se kritični dijelovi koji se dugo

izvršavaju. Nakon optimizacije, ti dijelovi se mogu smanjiti, a njihovo izvođenje ubrzati.

Takvi dijelovi se otkrivaju profiliranjem programskog koda. Promatranjem nekih

elemenata programskog koda na prvi pogled možete uočiti potencijalno kritična mjesta te

ih ispitujete prve.

3.6.1 Ulazno­izlazne operacije 

Neki od najvažnijih izvora neefikasnosti su nepotrebne ulazno-izlazne operacije (engl.

input-output, I/O). Na primjer, ako imate mogućnost izbora između obrade u memoriji

Page 21: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

16

nasuprot obradi korištenjem čvrstog diska ili web servisa, svakako izaberite rad u memoriji

osim ako je upotreba radne memorije kritična.

Sljedeći primjer potkrijepljen grafom s rezultatima mjerenja prikazanom na slici 3

prikazuje koliko je memorija znatno brža od I/O pristupa.

Prva metoda pristupa datoteci i čita znak koji se nalazi na slučajno izabranoj poziciji u

rasponu od 0 do 100. Svaki put prije nego pročitamo znak, moramo se pozicionirati na

poziciju na kojoj se nalazi.

Random r = newRandom(100); StreamReader f = newStreamReader(@"C:\Prijevod.txt"); for (int i = 0; i < brojac; i++){   f.BaseStream.Seek((long)r.Next(100), SeekOrigin.Begin);   char c = (char)f.Read(); } 

Druga metoda učitava datoteku izravno u memoriju u obliku niza. Zatim pristupa

slučajnom indeksu niza.

Random r = newRandom(100); StreamReader f = newStreamReader(@"C:\Prijevod.txt"); string memorijskiBuffer = f.ReadToEnd(); for (int i = 0; i < brojac; i++) {   char c = memorijskiBuffer[r.Next(100)]; }  Datoteka je slučajno izabrana tekstualna datoteka na mom računalu te je veličine 688 B.

Slika 3 Odnos brzine memorije i čvrstog diska

Prema ovim podacima i klasama za čitanje koje koristim, metoda koja pristupa izravno

memoriji je 35 puta brža.

5

175

0

50

100

150

200

1 2

Memorija

Datoteka

Page 22: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

17

Da smo kojim slučajem probali isto ovo napraviti uz dohvaćanje podataka s drugog

računala preko mreže, vrijeme izvršavanja bi bilo još veće jer bi trebalo uzeti u obzir i

vrijeme pristupa do ciljnog računala putem mreže. Rezultati koje smo dobili su dovoljni da

prilikom optimizacije detaljnije provjerimo sva mjesta u programu koja pristupaju čvrstom

disku i izbacimo sve pristupe koji nisu potrebni i mogu se odraditi u memoriji.

3.6.2 Straničenje 

Operacija straničenja (engl. paging) koja se koristi za izmjenu memorijskih stranica je

znatno sporija od operacije koja radi samo na jednoj stranici memorije. Nekad mala

promjena uzrokuje velike razlike. U sljedećem primjeru, ćemo vidjeti koje promjene koda

su bile potrebne da bi se izbjeglo straničenje. Imamo sistem koji barata sa stranicama

veličine 4 KB, a programski koji izgleda ovako:

for ( column = 0; column < MAX_COLUMNS; column++ ) {  for ( row = 0; row < MAX_ROWS; row++ ) {        table[ row ][ column ] = BlankTableElement();     }  }  

Na prvi pogled ne vidimo problem, iako on postoji. Problem je u tome što je svaki element

tablice duljine 4.000 B. Ako tablica ima previše redaka, svaki puta kada program treba

pristupiti drugom retku, operacijski sustav će promijeniti stranice memorije. Na način na

koji je ova petlja strukturirana, svaki pristup će uzrokovati promjenu stranice na disku.

Ako restrukturiramo petlju na ovaj način,

for ( row = 0; row < MAX_ROWS; row++ ) {  for ( column = 0; column < MAX_COLUMNS; column++ ) {        table[ row ][ column ] = BlankTableElement();     }  }  

dobit ćemo bolje performanse u vidu ne tako čestog straničenja.

3.6.3 Pozivi prema sistemu 

Pozivi prema sistemu su jako često skupi. Ove metode u sebi obično sadržavaju I/O

operacije prema čvrstom disku ili ulazno-izlaznim uređajima poput tipkovnice, monitora,

pisača te operacije za pristup memoriji. Ako su performanse problem onda je potrebno

Page 23: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

18

saznati koliko su skupi pozivi prema sistemu. Ako su skupi onda se mogu razmotriti

sljedeće opcije:

• Napisati vlastite servise. Nekada je potreban samo djelić funkcionalnosti koje pruža

sistemska metoda, i svu potrebnu funkcionalnost možete sami izgraditi. Pisanje

vlastitih servisa vam daje nešto što je manje, brže i odgovara vašim potrebama.

Nedostatak je što morate biti upoznati s pisanjem sistemskih programa što može

predstavljati problem za određenu skupinu razvojnika.

• Izbjegavajte dublji odlazak u sistem. Napravite jednostavniju obradu, ako je to

moguće.

• Surađujte s autorima sistema kako bi oni prilagodili njegovu funkcionalnost vašim

potrebama te kako bi pozivi prema sistemu bili brži. Većina isporučitelja sistema je

zahvalno na informacijama korisnika i posebno razvojnih timova o njihovom

sustavu koje će im pomoć u poboljšavanju istog.

3.6.4 Greške u programskom kodu 

Posljednji navedeni, ali nipošto najmanji ili najrjeđi izvor loših performansi su greške u

kodu. Uzroci takvih grešaka su brojni od nedovoljno kvalitetnog programskog koda, loše

iskonfiguriranih konfiguracijskih datoteka, loše dizajniranih klasa odnosno kompletne

strukture aplikacije, loše dizajnirane baze podataka sl.

3.7 Mjerenje 

Zbog toga što mali dijelovi koda tijekom izvođenja programa obično koriste

neproporcionalnu količinu resursa, izmjerite programski kod da biste našli uska grla.

Nakon što ste našli uska grla, optimizirajte ih pa izmjerite programski kod ponovno i vidite

koliko poboljšanje ste stvarno dobili. Pretpostavka dobrih rezultata je da mjerenja u oba

slučaja vršite u identičnim ili barem jako sličnim uvjetima i okruženju.

Kod optimizacije iskustvo ne pomaže previše. Iskustvo osobe može doći iz nekog starijeg

jezika ili prevoditelja, a kad se te stvari promjene onda iskustvo postane nevažeće. Nikad

ne možete biti sigurni u efekte optimizacije dok ih ne izmjerite.

Ako nije vrijedno mjerenja da bi saznali je li efikasnije, onda nije vrijedno žrtvovanja

čitljivosti da bi dobili na performansama.

Page 24: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

19

Mjerenja performansi moraju biti precizna [4]. Profiler je jako koristan alat koji dolazi s

većinom suvremenih razvojnih alata, pa tako i s MS Visual Studiom 2010 na kojem su

izrađeni i izmjereni primjeri navedeni u ovom radu.

Bez obzira koristi li se gotovo rješenje za mjerenje performansi ili vlastito koje smo sami

izradili, važno je da se mjeri samo onaj dio programskog koda koji se optimizira.

Instanciranje .NET klase štoperice na početku tog dijela koda i zaustavljanje na kraju je

dobra praksa koja daje zadovoljavajuće rezultate. U nekim slučajevima dijelove

programskog koda je potrebno izvesti više puta (npr 100.000 ili 1.000.000) kako bi se

dobili mjerljivi rezultati.

3.8 Iteracije 

Jednom kada identificirate usko grlo ostati ćete začuđeni koliko možete dobiti na

performansama optimizacijom koda. Teško da ćete korištenjem samo jedne tehnike ubrzati

programski kod deset puta ali ako kombinacijom više tehnika uz odgovarajući trud i

utrošak vremena možete dostići gotovo svaku brojku koja vam je potrebna odgovara.

Primjer iz stvarnog života je aplikacija koju sam radio. Bavi se planiranjem radnog

vremena. Jedan planer je imao dosta ljudi ispod sebe. Preko 100. Samo upisivanje u bazu

je teklo jako brzo ali čitanje je bilo izuzetno sporo. Zbog prirode problema, svako čitanje je

bilo popraćeno s velikim brojem validacija koje su gledale smije li radnik još raditi, da li je

radio prekovremeno ili je bio na službenom putu, itd. Ispisivanje 100 radnika za mjesec

unaprijed je trajalo preko 15 sekundi. Kombiniranjem tehnika optimizacije uspio sam

smanjiti to vrijeme na ispod 5 sekundi. Velika pomoć u svemu tome bio je Profiler koji je

došao uz Visual Studio. On je otkrio da je su metode za izvršavanje upita nad bazom

podataka i spajanje nizova metode koja se najviše pozivaju i najdulje traju. Tehnike koje

sam upotrijebio bile su rad sa memorijom umjesto čvrstim diskom te korištenje

StringBuilder klase o kojoj će biti riječi u narednim poglavljima.

3.9 Proces optimizacije koda 

Optimizacija koda je iterativni proces koji se upotrebljava kako bi se identificirala i

eliminirala uska grla u aplikaciji. Iterativan je jer ponavljamo korake dok nam aplikacija ne

dostigne odgovarajuće performanse.

Page 25: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

20

Na slici 4 prikazan je jednostavan proces optimizacije sa četiri koraka.

Slika 4 Ciklus optimizacije programskog koda

Nakon definiranja osnova što se optimizacijom želi postići, slijedi prikupljanje podataka o

trenutnom stanju i funkcioniranju programa. Prikupljeni podaci se analiziraju te se vrši

promjena programskom koda vodeći računa o dobivenim rezultatima i definiranom cilju.

Nakon svakog skupa promjena ponovno testiramo i ponovno mjerimo da bi se uvjerili da

program i dalje ispravno funkcionira te da se poboljšanje performansi kreće u željenom

smjeru

3.9.1 Definiranje osnova 

Prije nego se započne s optimizacijom potrebno je odredit osnovne postavke optimizacije

koja uključuje ciljeve optimizacije, plan testiranja i opis metrike koja će se koristiti pri

mjerenju ostvarenih rezultata.

3.9.2 Prikupljanje podataka 

Prilikom prikupljanja podataka za analizu vrlo je važno da se svi podaci prikupljaju iz istog

izvora te da se ne mijenja okruženje unutar kojeg se podaci prikupljaju. Opterećenje

sustava tijekom prikupljanja podataka treba biti konstantno ili približno konstantno. Na

takav način podaci koji se budu prikupljali i razlike među njima oslikavati će ostvarene

rezultate optimizacije, a neće uključivati druge vanjske faktore.

3.9.3 Analiza rezultata 

U ovom koraku se analiziraju prikupljeni podaci koji se upotrebljavaju kako bi se otkrila

uska grla. Da bi identificirali osnovni problem potrebno je početi tragati od mjesta na

Prikupljanje podataka

Analiziranje rezultataKonfiguriranje

Testiranje i mjerenje

Page 26: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

21

kojem su simptomi prvi put opaženi. Obično najočitija opažanja nisu korijen problema.

Kada analiziramo podatke trebamo imati na umu slijedeće [4]:

• Podaci koje se prikupe obično su samo indikator problema, a ne izvor problema.

Indikatori performansi izvođenja programa izraženi u milisekundama ili sekundama

mogu dati upute kako izolirati specifične skupine funkcionalnosti koje je potrebno

detaljnije proučiti u aplikaciji.

• Nagle promjene performanse aplikacije možda nisu jako važni već oslikavaju

promjene okruženja na koje optimizacijom ne možete utjecati.

• Zbog toga se treba pobrinuti da se testovi ne pokreću istovremeno s nekim drugim

aplikacijama. Pored toga testove treba izvoditi u odgovarajućem trajanju, te uzimati

u obzir prosjeke vremena izvođenja.

• Ako prikupljeni podaci nisu potpuni, onda će i analiza biti nepotpuna i netočna.

• Potrebno je imati mogućnost identificiranja i izoliranja dijelova aplikacije koje je

potrebno detaljnije optimizirati

• Ako se tijekom analize identificiraju uska grla koja znatnije utječu na performanse,

neka vam ona budu prioritet.

• Tijek analize je potrebno dokumentirati kako bi se naknadno isti postupak mogao

ponoviti.

3.9.4 Konfiguriranje 

Aplikacija se optimizira tako da se aktiviraju odgovarajuće opcije na razini sistema,

platforme ili promjene dijelovi programskog koda unutar same aplikacije. Dokumentacija

analize iz prethodnog koraka može tome pomoći. Pri konfiguriranju valja voditi računa o

dvije stvari:

• Potrebno je raditi jednu po jednu promjenu. Promjene moraju biti individualne.

Istovremeno izvođenje više promjena može znatno otežati identifikaciju promjene

koja je pridonijela povećanju performansa.

• Probleme je potrebno rješavati po određenom redu ovisno o postavljenim ciljevima

i očekivanim rezultatima. Prvo se treba posvetite problemima za koje se smatra da

će njihovo rješavanja najviše pridonijeti poboljšanju performansi.

Page 27: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

22

3.9.5 Testiranje i mjerenje 

Optimizacija je iterativan proces [4]. Nakon što su napravljene promjene u jednoj iteraciji,

potrebno je ponovno testirati aplikaciju i izmjeriti utjecaj napravljenih promjena na

performanse. Tako se dobiva informacija o tome jesu li napravljene promjene pomogle ili

odmogle. Taj proces treba ponavljati sve dok ostvareni rezultati ne dostignu zadane ciljeve.

Page 28: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

23

4 TEHNIKE OPTIMIZACIJE 

Optimizacija koda je dosta popularna tema u programiranju već dugi niz godina. Zbog

toga, jednom kada ste odlučili poboljšati performanse i želite to odraditi na nivou

programskog koda, na raspolaganju je bogat izbor isprobanih tehnika optimizacije.

Ovo poglavlje se fokusira na poboljšanje brzine te u sebi sadrži malo savjeta o tome kako

smanjiti veličinu programskog koda. Performanse se obično odnose na veličinu i brzinu, ali

veličina se obično smanjuje redizajniranjem klasa, a ne optimiziranjem programskog koda.

Optimiziranje koda se češće odnosi na male izmjene unutar programskog koda nego na

velike izmjene.

Glavna svrha ovog poglavlja je ilustriranje optimizacije koda koje se mogu primijeniti u

praktičnim situacijama. Neke knjige i članci predstavljaju tehnike optimizacije kao grubu

metodu procjene te sugeriraju kako će primjena određene tehnika proizvesti tražene

rezultate. Gruba metoda procjene se loše spaja sa optimizacijom. Jedina metoda procjene

koja je valjana je mjerenje rezultata. Zbog toga će ovo poglavlje sadržavati i listu stvari

koje možete praktično isprobati. Neke od tih tehnika vjerojatno neće raditi u vašem

konkretnom razvojnom okruženju ali druge će raditi jako dobro.

4.1 Logika 

Veliki dio programiranja se sastoji od manipuliranja programskom logikom. Ovaj dio

objašnjava kako manipulirati logičkim izrazima da bi poboljšali performanse.

4.1.1 Zaustavljanje grananja 

Recimo da imamo sljedeći izraz:

if ( x < 5 && x < 10)  Jednom kada smo shvatili da je x manji od 5, nije potrebno raditi ostatak provjere. Neki

programski jezici i prevoditelji daju mogućnost provjere poznate kao prekidanja provjere

kratkim spojem (engl. short-circuit evaulation). To znači da će prevoditelj tijekom

prevođenja sam generirati dio programskog koda koji će automatski zaustaviti testiranje

ako je odgovor poznat. Ako programski jezik ne podržava takvu provjeru, onda bi u

razvoju trebalo izbjegavati korištenje operatora „i“ i „ili“. Umjesto toga sami programeri

Page 29: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

24

trebaju dodati programski kod koji će oponašati funkcionalnost tih operatora. Sljedeći izraz

demonstrira primjer takvog programskog koda:

if ( x < 5){ if ( x < 10){  

Princip prekidanja testiranja kada znate odgovor je jako dobar u mnogo drugih slučajeva.

Petlja za pretragu je jedna od njih. Ako skenirate polje brojeva i tražite negativni broj te

trebate samo znati postoji li negativni broj u tom polju, onda se može provjeriti svaka

pojedina vrijednost te u slučaju kada je takav broj pronađen, postaviti varijablu na true.

Primjer takvog programskog koda je slijedeći:

bool imaLiNegativnih = false; for (int i = 0; i < m; i++) {   if (polje[i] < 0)   {     imaLiNegativnih = true;   } }  Bolji pristup bi bio algoritam koji zaustavlja pretragu u onom trenutku kada je pronađen

negativan broj.

Slika 5 Zaustavljanje ispitivanja kada je odgovor poznat

41

21

91 91

0

10

20

30

40

50

60

70

80

90

100

1/2 1/4

Break

Standard

Page 30: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

25

Na slici 5 su vidljivi rezultati ovakve optimizacije. Pretraga se odvija na polju sa

10.000.000 članova. U prvom slučaju negativan broj smješten je na polovicu polja, a u

drugom se nalazi u prvoj četvrtini polja. Duljina izvođenja u prvom slučaju je više nego

dvostruko kraća. Kada se negativan broj nalazi u prvoj četvrtini polja razlika u ubrzanju je

još vidljivija tako da se programski kod odvija gotovo četiri i po puta brže.

4.1.2 Lijena evaluacija 

Ako program koristi lijenu evaluaciju (engl. laizy evaluation) onda izbjegava raditi sve do

trenutka kada je ta obrada stvarno potrebna. Ovaj princip je jako sličan strategiji „u pravom

trenutku“ (engl. just in time, JIT) koja obradu odrađuje što je moguće bliže onom trenutku

kada je to potrebno.

Primjerice, imamo program koji koristi tablicu s 5.000 vrijednosti. Pri pokretanju programa

ta tablica se izgenerira. Dok program radi, on koristi samo jedan mali dio te tablice. Zbog

toga bi imalo više smisla da se s tim malim dijelom tablice radi onda kada su podaci

potrebni, a ne odjednom. Nakon završetka obrade, tablica se može spremiti za daljnju

uporabu postupkom privremenog spremanja (engl. caching).

4.1.3 Trenutna lokacija 

Ovo je princip kojim se različite akcije grupiraju i odrađuju odjednom [3]. Ovakav pristup

se često upotrebljava u programima koji učestalo pristupaju jednim te istim podacima.

Primjerice, metoda ima visok stupanj trenutnog lokaliteta ako se poziva puno puta u nekoj

vremenskoj jedinici. Imajući ovaj princip u vidu, možemo optimizirati razne programe.

Zamislimo da imamo program koji čita 100 datoteka, radi izvještaj za svaku datoteku i

onda piše 100 datoteka. Ovaj program se može napravit tako da se u petlju stave slijedeće

operacije.

Čitaj Napravi izvještaj Piši Čitaj Napravi izvještaj Piši ...  Drugi način je napraviti tri odvojene petlje te svakoj petlji pridružiti jednu operaciju.

 

Page 31: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

26

Čitaj Čitaj Napravi izvještaj Napravi izvještaj Piši Piši ...  Ovom tehnikom prvo čitamo 100 datoteka, zatim radimo 100 izvještaja te na kraju pišemo

100 datoteka. Jedan razlog zbog kojeg će ovo povećati performanse je način na koji I/O

predviđa zadatke.

Ako se čita 100 datoteka u petlji, sistem će predvidjeti naredne akcije te će moći poduzeti

mjere keširanja što će u konačnici rezultirati boljim performansama. Teorija prevoditelja

opisuje sve medije za pohranjivanje podataka kao što su memorija, keš procesora, čvrsti

diskovi i sl. Računala imaju hijerarhiju memorija te će se program izvršavati brže ukoliko

se predvidi ovakvo ponašanje.

4.2 Optimizacija nizova znakova 

Optimizacija nizova je vrlo važna stavka kada se optimizira jedan sustav. Razlog tomu je

raširenost nizova. Sustavi koji su potpora nekom poslovnom procesu moraju na neki način

komunicirati s korisnicima. Jedini načina je komunikacija putem tekstualnih informacija

koje se prikazuju u obliku niza znakova. Stoga bilo kakva optimizacija nad nizovima na

niskom nivou će rezultirat bržim sustavom u konačnici.

Da bi mogli optimizirat rad sa nizovima prvo je potrebno razumjeti na koji su način nizovi

zastupljeni u .NET-u. Kao prvo, nizovi su objekti tipa System.String ali su također po

nekim definicijama i vrijednosti isto kao i osnovni tipovi podataka int, long ili float.

Razlog za to su neki operatori koji se koriste nad ovim tipom objekata. Tako da, iako je

string referenca na objekt, operatori jednakosti (== i !=) su definirani tako da uspoređuju

vrijednosti samog objekta a ne reference. Također bi valjalo napomenuti da operator +

zbraja nizove a operator [] pristupa točno određenom znaku u nizu. Niz definiramo

ključnom riječi string. Vrijednost postavljamo pridruživanjem teksta u dvostrukim

navodnicima.

string str = "Tekst"; 

Page 32: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

27

Jednom kada kreiramo objekt tipa string on je nepromjenjiv. Sve metode koje rade s

nizovima vraćaju novi objekt tipa string i to je cijela bit optimizacije nizova znakova. U

slučaju da imamo niz1 i niz2 te ih spojimo operatorom += dobit ćemo novi objekt koji je

kombinacija ta dva niza te će niz1 sada pokazivati na novi objekt.

String niz1 = "crven"; String niz2 = "bijeli"; niz1 += niz2; // "crvenbijeli" 

Postoji više načina za spajanje nizova. Jedni su sporiji, a drugi brži. Valja napomenuti da

brzina ovisi o broju spajanja. Nizovi znakova se mogu spajati na različite načine:

1. Operator +;

2. Metoda String.Concat();

3. Metoda String.Format();

4. Metode klase System.Text.StringBuilder();

Ono što ću pokušati dokazati u narednim primjerima je da najbolji način ovisi o konkretnoj

situaciji odnosno o količini manipulacija sa nizovima.

4.2.1 Korištenje String.Concat metode 

4.2.1.1 Spajanje dva niza 

Spajanje dva niza se vrši pomoću operatora + ili metode String.Concat. Ova dva načina su

identična jer ako pri spajanju koristimo operator +, C# prevoditelj će taj operator pretvoriti

u poziv metode String.Concat tako je ovdje razlika samo u sintaksi.

4.2.1.2 Spajanje tri niza 

I u ovom slučaju najbolji izbor pri spajanju nizova je operator +.

4.2.1.3 Spajanje četiri niza 

Ovdje se već može osjetiti razlika. Možemo nastaviti spajati nizove pomoću operatora +,

međutim, ako spajamo nizove na takav način jednom, neće se primijetiti velika razlika kao

u slučaju ako uzastopno nadodajemo nizove korištenjem operatora +. Već se u ovom

primjeru može razmišljati o korištenju metoda klase StringBuilder.

Page 33: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

28

4.2.1.4 Spajanje pet nizova 

Kod spajanja pet nizova operatorom + performanse programa se drastično pogoršavaju.

Razlog leži u implementaciji ugrađenoj unutar .NET frameworka. Ako pogledamo

String.Concat metodu vidjeti ćemo da postoje opterećene (engl. overaload) metode. Za sve

slučajeve u kojima se vrši spajanje do četiri niza, postoje jasno definirane metode koja

primaju 2, 3 ili 4 parametra. Međutim, ako predamo više od 4 niza, pozvat će se

preopterećena metoda koja prima polje nizova [3], i. ta metoda radi sporije od prethodne tri

metode.

Iz gore navedenih scenarija jasno se vidi da je String.Concat metoda namijenjena za

spajanje od dva do četiri niza. Spajanje većeg broja nizova rezultirati će sporijim

spajanjem.

Primjer:

Sljedeći izraz će uvijek biti brži od bilo koje druge metode spajanja nizova. Razlog tomu je

optimiziranost same String.Concat metode koja najbrže radi kad se spajaju do četiri niza.

string rezultat = "prvi" + "drugi" + "treci" + "cetvrti"; 

Ako želimo postići isti rezultat korištenjem metoda klase StringBuilder dobiti će se sporiji

kod.

StringBuilder strBuil = newStringBuilder(); 

strBuil.Append("prvi"); strBuil.Append("drugi"); strBuil.Append("treci"); strBuil.Append("cetvrti"); 

Čak i u slučaju da se klasa StringBuilder inicijalizira sa zadanim kapacitetom, bit će

vidljiva sporost ovakvog pristupa.

4.2.2 StringBuilder klasa 

Nakon što je pojašnjen način funkcioniranja String.Concat metode, te su navedene njezine

prednosti, pojasniti ću StringBuilder klasu. Ova klasa služi za manipuliranje velikim

nizovima znakova te velikom količinom istih. Razlog zbog čega je ova klasa napravljena i

za što se koristi krije se u samoj definiciji klase koja služi za pohranu niza znakova

Page 34: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

29

System.String. Jednom kada inicijaliziramo niz znakova i pridružimo mu vrijednost, ta

varijabla je nepromjenjiva. Svaki puta kada koristimo metode nizova mi kreiramo novi

objekt u memoriji što uzrokuje novu alokaciju memorije za naš niz.

U situacijama u kojima moramo odraditi puno ponavljajućih operacija nad nizom,

preopterećenost koja se asocira s kreiranjem novog objekta može biti skupa u pogledu

resursa. Klasa StringBuilder se koristi kada se želi mijenjati sadržaj niza znakova, a da se

ne kreira novi objekt u memoriji. Ova klasa je izuzetno pogodna za ponavljajuća spajanja

kakva se događaju u petlji.

4.2.3 Kada koristiti StringBuilder klasu 

Da bi odgovorili na ovo pitanje potrebno je prvo odraditi neke testove. U slijedećim

testovima uspoređivati će se performanse metoda StringBuilder klase s metodom

String.Concat. Ovim pristupom ću pokazati razlike između ova dva načina spajanja nizova

znakova koje uvelike ovise o količini nizova koji se spajaju kao i o ispravnoj inicijalizaciji

StringBuilder klase.

Razlog zbog čega je klasa StringBuilder znatno brža od metode String.Concat pri većem

broju spajanja leže u različitosti između StringBuilder klase i System.String klase.

StringBuilder klasa:

• Ima promjenjive međuspremnike;

• Može se mijenjati bez kopiranja.

String klasa:

• Ima nepromjenjive međuspremnike;

• Ne može se mijenjati;

• Svaka operacija vraća novi string;

• Kopiranja u petljama mogu povećati potrebnu količinu memoriju.

Početna pretpostavka je da će klasa StringBulder imati bolje performanse od metode

String.Concat pri spajanju pet ili više nizova. Pored toga, klasa StringBuilder koja se bude

inicijalizirala s kapacitetom, biti će brža od klase StringBuilder koja se inicijalizira s

regularnim konstruktorom. Razlog zbog čega je to tako leži u načinu na koji operacijski

sustav upravlja memorijom i kolekcijama. Ako ne inicijaliziramo kapacitet onda će klasa

Page 35: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

30

svaki put kad dodamo još informacija pokušati realocirati novi komad memorije u koji će

smjestiti nove podatke. To izaziva povećane zahtjeve za memorijom. Ako predamo

parametar kapaciteta, onda govorimo klasi koliko će elemenata maksimalno moći pohraniti

prije nego realokacija dodatne memorije bude potrebna. U tom slučaju se odjednom na

početku alocira sva potrebna memorija i višestruki pristup nije potreban.

Zamislite StringBuilder s kapacitetom kao jednu praznu ljusku. Sve što dodajemo raste

unutar ljuske za razliku od StringBuildera koji nije inicijaliziran s kapacitetom, kod njega

bi se ljuska trebala svaki put nanovo izraditi.

Razlike u metodama spajanja na uzorku od n nizova te ponovljene 100.000 puta radi

dobivanja usporedivog vremena su vidljive na slici 6. Od početka je vidljivo da je

String.Concat() metoda najbolja do četiri niza. Nadalje, StringBuilder s kapacitetom je

uvijek bolji od StringBuilder-a bez kapaciteta. U slučaju dodavanja većeg broja nizova

tendencije linija bi se nastavile.

Slika 6 Odnos brzina različitih metoda pri spajanju više nizova

4.2.3.1 Korištenje pojedinačnih znakova 

U dosadašnjim primjerima vidjeli smo da metoda Append, klase StringBuilder, prima

nizove no ta metoda može primati i druge tipove podataka. Ta metoda ima sveukupno 19

preopterećenih metoda. Ovo je vrlo korisno znati kada pridodajemo neke druge tipove

podataka. Primjer koji slijedi bazira se na pretpostavci da će Append metoda klase

StringBuilder će brže nadodati pojedinačni znak 'i' nego niz znakova ''i''.

0

50

100

150

200

250

300

2 4 6 8 10 12 14 16 18 20

String.Concat()

StringBuilder()

StringBuilder(capacity)

Page 36: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

31

Prvo treba objasniti razliku između 'i' i ''i''. Prva vrijednost je pojedinačni znak tipa char te

će zauzimati samo 2 B memorije dok je druga tipa string te će zauzimat 20+(n/2)*4 B

memorije (n predstavlja broj znakova u nizu). Također valja istaknuti najvažniju razliku a

to je da je pojedinačni znak vrijednost, a niz znakova je referenca na objekt.

Rad s vrijednostima je uvijek brži od rada sa objektima stoga će korištenje vrijednosti u

Append metodi rezultirati kraćim vremenom izvođenja.

Kao što je vidljivo na slici 7, na 1.000.000 iteracija, metoda Append koji kao argument

prima karakter je brža od poziva metode Append koja prima niz.

Slika 7 Ispravna upotreba metode Append() kod rada s pojedinačnim znakovima

4.2.4 Pretvaranje integera u string 

Postupak pretvaranja cijelog broja u niz znakova je vrlo jednostavan postupak koji je

podržan različitim metodama unutar .NET frameworka. Svaka cjelobrojna varijabla ima

metodu koja vraća njezinu vrijednost u obliku niza. Ovo je potrebno iz više razloga od

kojih je jedna potreba za prikazivanjem vrijednosti broja na zaslonu. Vizualne kontrole

koje prikazuju brojeve i slova poput tekstualnog polja ili labele mogu prikazati samo

vrijednosti tipa string. Svi znakovi koji se iscrtavaju moraju se iz svog izvornog oblika

pretvoriti u niz znakova.

4.2.4.1 Metoda koja se inače upotrebljava. 

int cijeliBroj = 4; string broj = cijeliBroj.ToString(); 

U prethodnom isječku programskog koda inicijalizirana je varijabla tipa cijeli broj. Nakon

toga je inicijalizirana varijablu tipa string kojoj je pridružena vrijednost dobivena pozivom

ToString() metodu nad cijelim brojem. Ova pretvorba se odvija izuzetno brzo ali pitanje

38

56

0

10

20

30

40

50

60

1 2

Vrijednost

Referenca

Page 37: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

32

koje se postavlja pri optimizaciji je može li još brže? S ovim problemom se sreće svaki

programer pri radu s grafičkim korisničkim sučeljem zbog toga jer se ova metoda poziva

puno puta—svaki put kada je potrebno neku neznakovnu vrijednost prikazati na zaslonu U

narednim isječcima programskog koda i grafovima pokazati ću kako se ova operacija može

ubrzati i do četiri puta.

Pretpostavka od koje polazimo je da većinu vremena pri pretvaranju cijelog broja u

znakovni niz troši algoritam izračuna koji se pri svakom pozivu izvršava. Ako unaprijed

definiramo veliku količinu brojeva i njihove znakovne ekvivalente, postupak bi se mogao

znatno ubrzati.

Prvi korak je definiranje zasebne klase koja će u sebi sadržavati dvije metode. Jedna

metoda će inicijalizirati onoliko brojeva koliko ćemo koristiti, a druga metoda će tražiti te

brojeve.

Objekt koji će sadržavati naše vrijednosti biti će tipa Dictionary te će izgledati ovako:

static Dictionary<int, string> stringovi = new Dictionary<int,string>(); 

Rječnik pojmova će biti statičan tako da će se samo jednom napraviti i nakon toga biti

dostupan tijekom trajanja čitave sesije.

Nakon toga je potrebno napraviti metodu koja će biti ekstenzija na naš tip cjelobrojnog

podatka, tako da će biti dostupna iz svih programa sustava. Metoda će izgledati ovako:

public static string VratiString(this int broj) {   if (broj >= 0 && broj <= MAX)   {     return stringovi[broj];   }   return broj.ToString(); }  

Ako se parametar definira na takav način, programeri će prilikom razvoja programa u

Visual Studiu vidjeti napravljenu ekstenziju na način kako je to prikazano na slici 8. Tu

metodu će programeri moći izravno koristiti.

 

Page 38: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

33

Slika 8 Podrška od strane VisualStudia pri izradi ekstenzijskih(engl. extension) metoda

U našem slučaju u petlji od 65.535 brojeva inicijalizirati će se varijabla tipa string, te će za

svaki korak iteracije pozvati napravljena metoda. Isto će se ponoviti i za ToString()

metodu.

Za našu metodu niz se inicijalizira na slijedeći način::

string broj = cijeliBroj.VratiString();  Za standardnu ToString() metodu niz se inicijalizira ovako:

string broj = cieliBroj.ToString(); 

Rezultat koji se dobiva pri izvođenju 65.535 iteracija potvrđuje hipotezu. Naš rječnik će

prije dohvatiti definirane vrijednosti nego što će algoritam pretvoriti cijeli broj u niz.

Naravno ovo vrijedi samo za onaj raspon brojeva koji je definiran na početku izvođenja

programa.

Rezultat provedenog istraživanja vidljiv je na slici 9. Pretraživanje po rječniku je gotovo

četiri puta brže od algoritma pretvaranja brojeva u nizove.

Slika 9 Razlika u performansama nativne ToString() metode i naše implementacije

0

5

10

15

20

25

1 2

Lookup

Standard

Page 39: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

34

4.2.5 Petlje i pojedinačni znakovi 

Najgora moguća kombinacija su petlje i pretvaranje pojedinačnih znakova u niz da bi se

lakše provjerila jednakost nekog znaka s drugim znakom [3]. Dosta gotovih primjera

programskog koda na koje sam naišao koriste ovu lošu tehniku koja radi nepotrebne

alokacije memorije na gomili (engl. heap). Takve petlje se mogu pojednostavniti. Iako u

radu postoji poseban odlomak posvećen petljama mislim da je samo korištenje petlje

pokazatelj koliko je korištenje ToString() metode u nizu pogubno za performanse

aplikacije.

Inicijalno imamo dvije petlje koje služe za prolaz kroz niz znakova. Svaka petlja

uspoređuje trenutni znak s nekim drugim znakom—u našem slučaju znakom 'd'. Prva

petlja je puno brža jer radi manje alokacija na gomili uspoređujući znakove direktno.

Druga petlja radi sporije jer svaki put mora napraviti novi objekt tipa string. Ovo je očita

greška koju mnogi programeri često rade jer ne shvaćaju način funkcioniranja i alokacije

objekata.

Prva petlja:

for (int i = 0; i < niz.Length; i++){   if (niz[i] == 'g'){     brojac++;   } }  Druga petlja: for (int i = 0; i < niz.Length; i++){   if (niz[i].ToString() == "g"){     brojac++;   } }  Na slici 10 je vidljiva razlika u korištenju metode ToString() u usporedbi s indeksiranim

pristupom pojedinačnom znaku. Prva petlja je ujedno i brža jer se uspoređuju vrijednosne

varijable. Vrijednosne varijable za razliku od referentnih smještaju se na stog koji ne

zahtjeva čišćenje memorije od strane GarbageCollectora. Stog je ujedno i brži. Druga

petlja svaki znak prvo mora pretvoriti u niz. Za to je potrebna alokacija memorije na gomili

a potom se interno poziva metoda za uspoređivanje nizova. Degradacija u performansama

Page 40: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

35

je golema i prvi algoritam je gotovo pet puta brži. U ovom primjeru je odrađeno 100.000

iteracija nad nizom duljine 45 znakova.

Slika 10 Pokazatelj skupoće korištenja ToString() metode

4.3 Optimizacija petlji 

U ovom poglavlju opisati će se dvije petlje koje se danas najčešće koriste u .NET

frameworku s naglaskom na razlike u performansama. Petlje o kojima ćemo govoriti su for

i foreach.

4.3.1 For petlja 

U računalnoj znanosti for petlja je programska struktura koja dopušta da se programski kod

izvede uzastopce zadani broj puta. Ova struktura je klasificirana kao iteracijski izraz. Od

drugih struktura (poput while petlje) razlikuje se po eksplicitno definiranim brojačem koji

pokazuje odgovarajući korak iteracije. Ove petlje se koriste kada je broj iteracija poznat.

Sintaksno u C# programskom jeziku postoji više vrsta for petlji a ja ću obraditi for petlju s

tri izraza. Ova petlja je nasljedstvo programskoj jezika C te je karakteriziraju tri izraza;

inicijalizator, validator i brojač. Klasičan primjer ove petlje bi bio sljedeći:

for (int i = 0; i < 10; i++) {   //tijelo petlje } Petlja se izvršava deset puta. Brojač je na početku nula i u svakom prolazu kroz petlju se

povećava za jedan. Kada vrijednost brojača dođe do deset, izvođenje se prekida.

63

292

0

50

100

150

200

250

300

350

1 2

Index

ToString()

Page 41: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

36

4.3.2 Foreach petlja 

Ovaj izraz je idiom za iteraciju kroz stavke kolekcije. Foreach petlja se obično koristi

umjesto obične for petlje. Za razliku od ostalih petlji foreach petlja nema eksplicitno

definiran brojač nego radi na način da „radi dok ne odradiš za sve stavke“ umjesto klasično

definiranih petlji koje kažu „odradi n puta“. Ovakvom sintaksom izbjegava se tzv. „off-by-

one“ greška. Klasičan primjer foreach petlje je sljedeći izraz:

foreach (Stavka s in kolekcija.Stavke) {   //tijelo petlje } 

Prolazi se kroz sve elemente kolekcije stavki. U svakom prolazu trenutni element se

smješta u objekt tipa Stavke.

4.3.3 Razlika u brzini između for i foreach 

For i foreach petlje u programskom jeziku C# imaju različite karakteristike, a prema tome

i različite performanse. Pošto foreach petlja koristi više varijabli, koristi i više memorije na

stogu. Zbog toga možemo pretpostaviti da će u izvršavanju biti sporija od for petlje. Na

jednostavnom primjeru izmjeriti ćemo njihove performanse kako bismo potvrdili našu

pretpostavku da for petlja iterira kroz polja brže nego foreach kroz listu. Također ću

pokazati da for petlja brže iterira kroz listu od foreach petlje.

Napisat ćemo dvije metode. Obje metode će primati argument tipa int[]. Razlika će biti u

tome što će prva metoda iterirati for petljom a druga metoda foreach petljom.

Na slici 11 je vidljivo da je for petlja brža pri izvođenju od foreach petlje za otprilike 10%.

Slika 11 Razlika u brzini for i foreach petlji

1116

1274

1000105011001150120012501300

1 2

for

foreach

Page 42: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

37

4.3.4 Dekrementacija u for petlji 

U narednim odlomcima ću pokušati primijeniti dekrementaciju i testiranje na nulu.

Umjesto da inkrementiramo od nule do maksimalne vrijednosti, dekrementirati ćemo od

maksimalne vrijednosti do nule. Ova optimizacija je takozvana mikro optimizacija te

proizlazi iz načina na koji x86 čipovi testiraju neki broj na nulu [3]. Sve što trebamo

napraviti je promijeniti klasični izraz kojeg susrećemo u for petlji:

for (int a = 0; a < max; a++) u

for (int a = max ‐ 1; a >= 0; ‐‐a)  Ovakva optimizacija će rezultirati malo bržim izvođenjem petlje. Na 10.000.000 stavaka

dobit ćemo malo ubrzanje od otprilike 5% što je prikazano na slici 12.

Slika 12 Prikaz specifičnosti x86 arhitekture na performanse kretanja kroz petlju

4.3.5 Fuzija petlji 

Ova je tehnika s kojom se više petlji zamjenjuje jednom petljom. Podobna je u situacijama

kad više polja ima jednaku duljinu. Zanimljivo je da u ovakvim situacijama ova tehnika

može povećati brzinu izvođenja preko 30%. U slijedećem primjeru ćemo spojiti dvije

petlje te ćemo grafom prikazati ubrzanje koje smo dobili.

Ako imamo dva polja cijelih brojeva definirana kao:

int[] poljeA = newint[max]; int[] poljeB = newint[max];  Te ako želimo obaviti operaciju nad svakim elementom od oba dva polja, možemo to

obaviti odvojeno. Po jedna for petlja za svako polje. U svakoj od tih for petlji obavljamo

operacije nad našim poljem. Isto tako možemo stavit oba sva izraza, koja smo mislili

koristiti odvojeno, u istu for petlju. Ja ću radi primjera i jednostavnosti obaviti samo

730

740

750

760

770

1 2

Dekrementacija

Inkrementacija

Page 43: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

38

inicijalizaciju elemenata. PoljeA ću inicijalizirati na 1, a poljeB ću inicijalizirati na 2.

Inicijalizacija polja bi u neoptimiziranom primjeru izgledala ovako:

for (int i = 0; i < max; i++)   poljeA[i] = 1; for (int i = 0; i < max; i++)   poljeB[i] = 2;  Zatim ćemo spojiti ove dvije petlje u jednu te ćemo dobiti sljedeći izraz;

for (int i = 0; i < max; i++) {   poljeA[i] = 1;   poljeB[i] = 2; }  

Rezultati su vidljivi na slici 13. Pri 10.000.000 iteracija ćemo dobiti znatno, gotovo

dvostruko ubrzanje imajući u vidu da smo samo inicijalizirali elemente polja.

Slika 13 Spajanje petlji kao tehnika optimizacije

4.3.6 Odmotavanje petlji 

Odmotavanje petlji je tehnika koja se sama od sebe nameće programeru. Cilj odmotavanja

petlji je povećanje brzine izvođenja same petlje na način da se smanji ili eliminira broj

instrukcija koje kontroliraju tok petlje [3]. Takve instrukcije su pokazivačka aritmetika ili

testiranje na kraja petlje za svaku iteraciju . Kad kažemo da se petlja odmota mislimo na

sljedeće:

      

0

50

100

150

200

1 2

Spojene petlje

Odvojene petlje

Page 44: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

39

for (int i = 0; i < a.Length; i += 2) {   if (a[i] == 3)   {     // ...   }   if (a[i + 1] == 3)   {     // ...   } } 

U prethodnom primjeru smo ispitivali da li je broj u polju jednak broju 3. Umjesto da

prođemo kroz sva polja jedan po jedan mi ćemo preskakati po dva polja, a unutar grananja

ćemo svaki put uvećavati iterator za jedan. Ovakav način optimizacije će biti najbolji ako

unaprijed znamo broj iteracija, inače postoji mogućnost da dobijemo iznimku pri

pristupanju indeksu koji ne postoji. Primjer bi bio polje od 10 elemenata i odmotavanje sa

odstupanjem od 3. Lako je uočiti da jednom kad bi počeli ići dalje od devetog elementa da

ne bi mogli pristupiti 11 ili 12 elementu. Da bi to izbjegli trebali bi provodit ispitivanja.

Takva ispitivanja bi koštala resursa te bi takvo odmotavanje bilo skuplje ali opet brže.

Na slici 14 prikazani su rezultati mjerenja na 10.000.000 iteracija. Iz prikazanih rezultata

se može vidjeti da su performanse optimiziranog programskog koda za oko 50% bolje od

neoptimiziranog.

Slika 14 Odmotavanje petlji kao tehnika optimizacije

4.3.7 Unswitching 

Switching se odnosi na pravljenje odluka unutar petlje svaki puta kada se izvrši. Ako se

odluka ne mijenja dok se petlja izvršava može se obaviti takozvani „Unswitch“. To je

0

2

4

6

8

10

12

1 2

Odmotana

Uobičajena

Page 45: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

40

tehnika kojom se odluke stavljaju izvan petlje radije nego u petlji. Evo primjera koda koji

zavisno o tipu sumiranja sumira brojeve u jednu ili drugu varijablu:

for (int i = 0; i < brojac; i++){   if (TIP_SUME == 1)   {     sumaNeto += i;   }   else   {     sumaBruto += i;   }   } 

U prethodnom odlomku programskog koda, dio koji testira da li je suma tipa 1 će se odviti

za svaku iteraciju te će biti ista za vrijeme cijelog iteriranja. Programski kod se može

napisati i drugačije, tako da se testiranje na tip sume odvija samo jedanput što će

doprinijeti brzini izvršavanja

if (TIP_SUME == 1) {   for (int i = 0; i < brojac; i++){     sumaNeto += i;   } } else{   for (int i = 0; i < brojac; i++){     sumaBruto += i;   } } Rezultati mjerenja prikazani na slici 15 pokazuju da se optimizirani programski kod izvodi

gotovo dvostruko brže od neoptimiziranog.

Slika 15 Unswitching kao tehnika optimizacije.

57

107

020406080

100120

1 2

Unswitch

Standard

Page 46: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

41

4.3.8 Manje posla unutar petlji 

Jedan od ključeva u pisanju efektivnih petlji je smanjivanje posla koji se treba obaviti. Ako

možete obaviti izraz, test ili bilo što drugo izvan petlje onda to uradite [1]. To je dobra

programerska praksa.

Recimo da imamo varijablu unutar neke klase. Ta klasa je referencirana unutar druge klase

koja se upotrebljava unutar petlje. Na našem primjeru ćemo pokušati 1.000.000 puta

pomnožiti broj s koeficijentom popusta.

for (int i = 0; i < brojac; i++){   suma = i * stopa.Popust.Koeficijent; } 

Iz gore navedenog dijela programskog koda vidljivo je da svaki put kada trebamo

pomnožiti korak naše iteracije sa koeficijentom, moramo ići kroz dva objekta. Da smo

kojim slučajem novoj varijabli izvan petlje pridodali vrijednost koeficijenta te radili s tom

varijablom, dobili bi veliko poboljšanje performansi.

decimal koeficijent = stopa.Popust.Koeficijent; for (int i = 0; i < brojac; i++){   suma = i * koeficijent; } 

Dodatna varijabla koju smo uveli i inicijalizirali je van petlje ubrzala nam je samu petlju

dvostruko što je vidljivo na rezultatima mjerenja prikazanima na slici 16.

Slika 16 Izbacivanje posla iz petlje.

4.4 Polja 

Polja daju osnovnu funkcionalnost grupiranje više elemenata istoga tipa. Svaki programski

jezik implementira polja na neki način, a karakteristike implementacije polja nevezana za

vrstu .NET jezika su slijedeće:

97

206

0

100

200

300

1 2

Izvan petlje

Unutar petlje

Page 47: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

42

• Polja imaju stalni kapacitet. Kapacitet polja ostaje ista nakon inicijalizacije. Ako

želite povećati kapacitet morate stvoriti novo polje željenog kapaciteta te nakon

toga kopirati prethodno polje u novo polje.

• Polja podržavaju indeksirani pristup. Ako želite pristupiti elementu polja možete

mu pristupiti putem njegovog indeksa.

• Polja podržavaju numerirani pristup. Možete pristupiti elementima polja tako što

ćete prolaziti kroz elemente foreach petljom.

• Memorija zauzeta poljem je neprekidna. .NET virtualni stroj (Common Language

Runtime, CLR) alocira polja na način da nema fragmenata u memoriji. Time se

osigurava veću brzina pristupa elementima polja.

U nastavku ću objasniti na koji način upotrebljavati polja da bi se dobilo na

performansama. Sama optimizacija polja se može obaviti vodeći računa o nekoliko

elemenata:

• Odabirite polja umjesto kolekcija;

• Upotrebljavajte čvrsto definirana polja u vidu tipova podataka;

• Upotrebljavajte krnja polja umjesto višedimenzionalnih polja.

4.4.1 Polja umjesto kolekcija 

Od svih kolekcija, polja su najbrža. U slučaju da ne trebate neku posebnu funkcionalnost

poput dinamičke ekstenzije nad kolekcijama, onda bi bilo bolje da upotrebljavate obična

polja. Također, pri korištenju čvrsto definiranih polja izbjegavaju se operacije pakiranje i

raspakiravanje (engl. boxing and unboxing).

4.4.2 Čvrsto definirana polja 

Koristite čvrsto definirana polja gdje god možete [1]. Ovim izbjegavate konverziju između

tipova podataka. U slučaju da definirate polja objekata i date mu vrijednost tipa integer

onda će se morati izvršiti pakiranje. To je proces u kojem sustav implicitno pretvara

podatak iz jednog tipa u drugi.

Na 1.000.000 iteracija ćemo inicijalizirati polje od 1.000.000 elemenata. Prvo polje će biti

tipa int[] a drugo polje će biti tipa object[]. Vrijednost koju ćemo pridruživati jednim i

drugim poljima će biti tipa int. U drugom polje će pri svakoj inicijalizaciji morati obaviti

pretvaranje iz tipa object u tip int. Na slici 17 je vidljiva golema razlika između ta dva

Page 48: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

43

pristupa i koja ilustrira zbog čega je važno koristiti čvrsto definirana polja kad god je to

moguće.

Slika 17 Izbjegavanje pakiranja(engl. boxing).

4.4.3 Krnja polja 

Krnja polja su polja sastavljena od drugih polja. Zašto ne reći višedimenzionalna polja?

Višedimenzionalna polja uključuju iste dimenzije. Ako definirate višedimenzionalno polje

4*5 onda znate da će svaki redak imati 5 stupaca. Krnja polja nisu takva i ona mogu imati

retke različitih duljina. Postavlja se pitanje namjene krnjih polja. MSIL (Microsoft

Intermediate Language) u koji se prevode instrukcije svih .NET programskih jezika ima

specifične instrukcije koje omogućuju optimizirani rad s jednodimenzionalnim poljima.

Nasuprot tome postoje višedimenzionalna polja kojima se pristupa istim kodom kojim se

pristupaju ostalim tipovima kolekcija i taj programski kod nije optimiziran za specifični tip

podataka. Slijedeći primjer prikazuje način na koji se definira krnje polje.

string[][] Address = newstring[2][];     // Krnje polje nizova  Address[0] = newstring[1]; Address[1] = newstring[2]; Address[0][0] = "Mrezni servisi"; Address[1][0] = "Programsko inzenjerstvo"; Address[1][1] = "Baze podataka I"; 

4.4.4 Spljošćivanje polja 

Spljošćivanje polja je tehnika kojom se dvodimenzionalna polja pretvaraju u

jednodimenzionalna. Slijedeći programski kod prikazuje način na koji se upotrebljavaju

spljoštena polja.

Inicijalizacija:

int[] polje = newint[duljina * visina]; 

0

20

40

60

80

100

int[] object[]

Čvrsto definirana

Pakiranje

Page 49: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

44

Pristupanje nekom elementu polja:

polje[(Y * duljina) + X]; 

U dvodimenzionalnim poljima elementima pristupamo putem njihovih koordinata X i Y.

Zbog toga se pri pristupu pojedinom članu provode dvije provjere. Na primjeru ću pokazati

kako je pristup elementima dvodimenzionalnog polja dimenzija 1.000*1.000 elemenata

sporiji od pristupa jednodimenzionalnom polju dimenzija 1.000.000 elemenata.

Slika 18 prikazuje vrijeme trajanja pristupa elementima na ta dva načina. Vrijeme trajanja

pristupa podacima jednodimenzionalnog polja je gotovo 50% brže od pristupa podacima

dvodimenzionalnog polja.

To je korisno znati kod izrade matematičkih programa koji naveliko koriste

dvodimenzionalne matrice. Dvodimenzionalna polja su zbog provjera i operacija nad njima

sporija pri korištenju od jednodimenzionalnih polja. Sporost je prvenstveno uzrokovana

vremenom potrebnim za pristup indeksu odgovarajućeg elementa.

Slika 18 Spljoštivanje polja kao tehnika optimizacije.

4.5 Tipovi podataka 

Promjene tipova podataka može znatno utjecati na smanjenje veličine programa i

povećavanju njegove brzine. Nekoliko načina kako se putem tipova podataka i polja mogu

povećati performanse pojedinog programa su slijedeći:

• Zbrajanje i množenje je puno brže korištenjem cijelih brojeva nego decimalnim

brojevima.

• Manje dimenzije polja omogućuju veću brzinu izvođenja programa.

• Manje pristupanja elementima polja osigurava veću brzinu izvođenja programa.

7

11

024681012

1 2

Spljoštena

Dvodimenzionalna

Page 50: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

45

4.5.1 Dodatni podaci 

Korištenje dodatnih podataka u obliku indeksa znači dodavanje informacija koje će

olakšati korištenje toga tipa podatka. Dodatni podaci se mogu dodati tipu podatka ili se

mogu čuvati u paralelnoj strukturi.

4.5.1.1 Duljina niza 

Primjeri korištenja dodatnog podatka mogu se naći u različitim vrstama nizova. U

programskom jeziku C, niz znakova je polje pojedinačnih znakova zaključeno sa znakom

\0. Da bi se izračunala duljina niza moramo proći kroz svaki znak sve dok ne dođemo do

znaka \0. U tom trenutku ćemo znati koliko je dugačak niz. U slučaju C# programskog

jezika postoji podatak na početku niza koji nam govori poje je duljine taj niz. Za razliku od

jezika C gdje treba izbrojati pojedinačne znakove, u programskom jeziku C# potrebno je

samo pročitati prvi podatak niza.

Sa stajališta ekonomičnosti korištenja tipa podatka, znatno je efikasnije održavati podatak

o duljini niza nego ga računati svaki puta kada je taj podatak potreban.

4.5.1.2 Neovisne, paralelne strukture indeksa 

Nekada je puno lakše i efikasnije manipulirati sa indeksima tipova podataka nego sa

samim podacima. Ako su informacije u našim tipovima podataka velike i zahtjevne pri

pomicanju (npr. Prijenos na drugu lokaciju čvrstog diska ili na drugu mrežnu lokaciju),

onda će sortiranje i traženje po indeksu biti puno brže nego direktno manipuliranje

podacima. U slučaju da je svaki podatak velik, možemo kreirati dodatnu strukturu koja

sadrži pokazivače na detaljne informacije. U ovom slučaju sve pretrage i sortiranje će se

vršiti u memoriji, a čvrstom disku se treba pristupiti samo jedanput, kada znamo točnu

lokaciju datoteke. Taj princip na primjer koriste relacijske baze podataka kako bi se

ubrzalo pretraživanje indeksiranih podataka.

4.5.2 Keširanje 

Keširanje podataka znači čuvanje istih na način da se do njih lako može doći kada

zatrebaju [1]. Ako npr. PC-kasa svaki put kada radite novi račun mora ići u bazu i

dohvaćati artikle to će možda usporiti rad programa. Ali ako dohvatite sve artikle u

memoriju, onda brzo i efikasno možete pretraživati po njoj. U slučaju da netko unese novi

artikal i mi ga zatrebamo, a artikal nije u kešu onda će naravno biti potrebno otići u bazu

Page 51: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

46

podataka. No to su granični slučajevi u keširanju zbog kojih performanse neće patiti kada

gledamo cjelinu.

Poboljšanje performansi uslijed keširanja informacija ovisi o tome koliko je sama

informacija tražena. Općenito gledajući, ako je potrebno više vremena da se generira novi

element te ako se taj element često generira onda je pametno keširati ga. Što je jeftinije

doći do novog elementa i sačuvati ga u privremenoj memoriji to je primjena tog postupka

vrjedniji. Kao što je slučaj s drugim vrstama optimizacije, tako je i kod ove složenost

izrade i podložnost greškama vrlo izražena.

Princip keširanja podataka danas se u velikoj mjeri koristi kod dohvaćanja podataka iz web

aplikacija. Pošto je dohvat podataka iz baze vrlo zahtjevna operacija, a u web aplikaciji u

kratkom vremenu može doći velik broj upita, podaci se pri prvom upitu keširaju. Nakon

toga određeno vrijeme će svi upiti dohvaćati podatke, ne izravno iz baze već iz privremene

memorije u koju su oni prethodno spremljeni. Na takav način se omogućuje da web

aplikacije uspješno opslužuju velik broj paralelnih upita.

4.6 Izrazi 

Dosta posla u programima se odrađuje putem matematičkih i logičkih izraza. Složeni izrazi

su skuplji gledajući resurse koje troše. U ovom potpoglavlju su prikazani načini

optimizacije matematičkih i logičkih izraza.

4.6.1 Algebarski izrazi 

Algebarski izrazi se mogu koristiti kako bi se skuplje operacije zamijenile jeftinijima.

Primjerice, sljedeći izrazi su logički jednaki.

( !a && !b ) !( a && b )  Ako se izabere druga operacija štedi se jedna operacija negacije. Iako ušteda jedne

operacije negacije na prvi pogled može izgledati nevažna, u praksi se događa upravo

suprotno. Zamislite matematički program koji mora napravit 10.000.000 ovakvih

operacija.

Page 52: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

47

Na slici 19 je prikazana razlika između programskog koda koji u izrazima upotrebljava

jednu odnosno dvije negacije. Program koji koristi samo jednu negaciju gotovo 50%

efikasniji od programa koji koristi dvije negacije.

Slika 19 Prikaz skupoće korištenja izraza na primjeru negacije

4.6.2 Smanjivanje snage 

Smanjivanje snage znači zamjenu skupljih operacija jeftinijima. Neke od mogućih zamjena

su slijedeće:

• Zamijenite množenje nizom zbrajanja.

• Zamijenite potenciranje nizom množenja.

• Zamijenite double, float i decimal tipove podataka int-om

• Zamijenite množenje i dijeljenje cijelih brojeva zbrajanjem s posmicanjem bitova.

4.6.3 Inicijalizacija pri prevođenju 

Ako se neka varijabla koristi u dosta poziva metoda, te ako se ta varijabla nikad ne mijenja

onda bi je bilo dobro definirati kao konstantu. To se također odnosi na operacije poput

množenja, dijeljenja, zbrajanja i sl. Bilo koju operaciju koja se puno poziva te se svaki put

mora izračunati, a nikada se ne mijenja je najbolje izračunati unaprijed i definirati ju kao

konstantu. Primjer bi bio računanje broja PI svaki put kada nam zatreba. Broj PI se može

izračunati putem različitih nizova no najbolje bi bilo izračunati ga odmah i staviti u

konstantu.

Recimo da imamo metodu koja računa PI putem Newtonove formule:

2!

2 1

73

110

0

20

40

60

80

100

120

Negacije Negacije

Jedna

Dvije

Page 53: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

48

Tu metodu ćemo realizirati putem rekurzije. Prva metoda poziva drugu metodu koja je

rekurzivna i poziva samu sebe određen broj puta. Rekurzivna metoda zbraja podatke dok

prva metoda množi dobiveni rezultat s dva i vraća konačan rezultat.

static double PI(){   return 2 * F(1); }  static double F(int i){   if (i > 60)  {     return i;   }   else  {     return 1 + (i / (1 + (2.0 * i))) * F(i + 1);   } } 

Ova metoda je vrlo spora zbog rekurzivnog algoritma. Nerekurzivni algoritam bi donekle

ubrzao trajanje operacije, ali ostaje činjenica da ćemo kod npr. 100.000 poziva ove metode

potrošiti znatno više vremena nego ako jednom izračunamo rezultat i pohranimo ga kao

konstantu. Na slici 20 je vidljiva golema razlika između ta dva pristupa.

Slika 20 Povećanje performansi predkalkuliranjem vrijednosti i korištenjem konstanti

Na programeru je odluka o tome hoće li se podatak računati dok radite ili će se prvo

izračunati, sačuvati i nakon toga koristiti kada zatreba. Ako se rezultati upotrebljavaju

mnogo puta možda je bolje jednom ih izračunate i sačuvati, te ih naknadno koristiti kada

zatrebaju. Ova odluka se manifestira na nekoliko načina. Najjednostavniji način je da se

izračuna ono što je potrebno van petlje, kao što je pokazano u poglavlju o petljama. Na

mnogo kompliciranijem nivou, možda će biti potrebno napraviti cijelu tablicu već

izračunatih podataka kada se program pokrene (prethodno opisani primjer pri pretvorbi

cijelih brojeva u znakovni niz podataka). Podaci se mogu zapisati u datoteku, bazu

podataka ili se izravno uvrstiti u program (engl. embed).

2

528

0100200300400500600

1 2

Konstanta

Metoda

Page 54: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

49

Optimiziranje tehnikom izračunavanja prije korištenja ima nekoliko oblika:

• Računanje rezultata prije nego se program pokrene i inicijalizacija konstanti tim

rezultatima.

• Računanje rezultata prije nego se program pokrene i pridruživanje tih rezultata

običnim varijablama.

• Računanje rezultata prije nego se program pokrene, pisanje podataka u datoteku i

čitanje datoteke pri pokretanju programa.

• Računanje rezultata jedanput kada se program pokrene i korištenje istih tijekom

rada programa.

• Računanje što je više moguće van petlje smanjujući posao unutar petlje.

• Računanje podataka kad su prvi put potrebni i korištenje istih tijekom rada

programa.

4.6.4 Eliminiranje podizraza 

Ako u programu postoji izraz koji se ponavlja više puta, bolje ga je pridružiti varijabli i

koristiti varijablu umjesto izraza. Na takav način smanjuje se višestruka evaluacija samog

izraza. Pogledajmo primjer programskog koda u kojem se više puta koristi isti izraz.

a = b * c + g; d = b * c * d; 

Taj programski kod se može promijeniti na slijedeći način čime se broj operatora u

izrazima smanjuje za jednu operaciju množenja. U prethodnom potpoglavlju ovog

poglavlja koje se bavi algebrom u optimizaciji, opisano je što znači ušteda pri korištenju

operatora.

tmp = b * c; a = tmp + g; d = tmp * d; 

4.7 Metode 

Metode ili funkcije su skup instrukcija koje se često izvršavaju. To je poprilično jasno te na

prvi pogled izgleda kao da se ne može baš puno optimizirati. U slijedećim odlomcima ću

pokazati suprotno. Ono što je primamljivo kod optimizacije metode je to što se metode

potencijalno mogu pozivati veliki broj puta. Ostvareno malo poboljšanje performansi

Page 55: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

50

kritičnih metoda koje se pozivaju mnogo puta može donijeti znatno kraće manje vrijeme

izvršavanja na razini cjelokupnog sustava.

4.7.1 Ručno kreiranje inline metoda 

U raznim verzijama C i C++ jezika, inline metoda je metoda nad kojom prevoditelj

izvršava inline ekspanziju. To znači da programer zahtjeva od prevoditelja da umetne

cijelo tijelo metode na svako mjesto gdje je ta metoda pozvana radije nego da generira kod

na mjestu poziva metode. C# ne dopušta programeru da definira metodu na taj način jer je

prevoditelj dovoljno „pametan“ da sam shvati treba li izvršiti takvu operaciju nad

metodom. Ono što programer može napraviti da zaobiđe takvo ponašanje prevoditelja je

ručno kopiranje tijela funkcije na svako mjesto gdje se funkcija izvršava.

U našem primjeru ćemo napraviti 3 metode.

1. Prva metoda će biti ručno kreirana inline metoda koja će inkrementirati vrijednost

dvadeset puta.

2. Druga metoda će inkrementirati vrijednost deset puta.

3. Treća metoda će inkrementirati vrijednost deset puta a zatim će pozvati drugu

metodu.

static int InkrementirajInline(int v) {   // Ovo je metoda koja ce se jedanput pozvati   // Sadrzava 20 inkrementiranja   v++; v++; v++; v++; v++; v++; v++; v++; v++; v++;   v++; v++; v++; v++; v++; v++; v++; v++; v++; v++;   return v; }  static int Inkrementiraj2(int v) {   // Ova metoda radi deset inkrementiranja   // zatim radi jos deset inkrementiranja putem druge metode   v++; v++; v++; v++; v++; v++; v++; v++; v++; v++;   v = Inkrementiraj1(v);   return v; }      

Page 56: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

51

static int Inkrementiraj1(int v) {   // Ova metoda radi deset inkrementiranja   v++; v++; v++; v++; v++; v++; v++; v++; v++; v++;   return v; }  Na primjeru 1.000.000 poziva metode InkrementirajInline i Inkrementiraj2 vidljiva je

razlika u vremenu poziva. Dobivamo rezultat koji je vidljiv na slici 21. Inline metoda je

brža za 32 nanosekunde po pozivu.

Slika 21 Performanse pri korištenju inline metoda

U ovom primjeru radi se o tome da u strukturnom programiranju, programeri koriste

funkcije kako bi organizirali složene programe, ali u samoj jezgri računala izvršavaju se

pojedinačne instrukcije a ne skupine opisane funkcijom. Prevoditelj prevodi poziv funkcije

u nizove instrukcija koje se izvršavaju na stogu (engl. stack). To objašnjava na

konceptualnom nivou zbog čega su inline metode brže. JIT (Just In Time) prevoditelj će pri

prevođenju pokušati napraviti metodu inline ali u osnovi to radi samo sa malim metodama.

4.7.2 Statične metode 

Kada je metoda u svome potpisu ima ključnu riječ static onda se kaže da je ta metoda

statična. Statična metoda se ne izvršava nad određenom instancom metode te je nemoguće

unutar takve metode referirati objekt s ključnom riječi this. Metoda instance operira nad

instancom objekta a toj instanci se može pristupiti sa ključnom riječi this.

Statične metode su u osnovi uvijek brže od metoda instanci. Postoji nekoliko razloga zašto

je to tako. Metode instanci će u najboljem slučaju biti pozvana s ključnom riječi this tako

da će takva metoda uvijek imat vrijeme poziva sporije za onoliko koliko je potrebno za

izračun referenca metode. Također, metode instanci su u .NET CIL međujeziku

implementirane s instrukcijom virtualnog poziva (engl. call virtual) callvirt, a ta instrukcija

107

139

0

50

100

150

1 2

Inline

Uobičajeno

Page 57: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

52

zahtjeva dulje vrijeme izvođenja. Iako je samo izvršavanje statične metode i metode

instance isto, razlika je u pozivanju metode. Razlika koju dobijemo pri testiranju,

prikazana na slici 22 odnosi se na pozivanje metode.

Slika 22 Dominacija statičnih metoda pri mjerenju brzine poziva

4.7.3 Manji broj parametara u metodama 

Jedan od načina na koji se metoda može optimizirati je smanjenje broja parametara čime se

smanjuje korištenja memorije na stogu (engl. stack).

static int Metoda1(int a, int b, int c, int d, int e, int f)  static int Metoda2(int a, int b, int c) 

Vrlo je važno tijekom testiranja rezultata optimizacije izbaciti jednaku iznimku unutar obje

metode. To je potrebno kako bi se prevoditelj zbunio te kako ne bi sam optimizirao metodu

te je napravio inline.

Slika 23 Utjecaj smanjivanja broja parametara na brzinu poziva metode

Prva metoda prima neke parametre koje ne koristi te će na 10.000.000 iteracija biti sporija

za gotovo 10% po pozivu odnosno oko 3.5 nanosekundi po pozivu metode. Na slici 23 vidi

se koliko je puta brža druga metoda, mjereno u milisekundama.

310

320

330

340

350

1 2

Statična

Instancirana

354

389

330

340

350

360

370

380

390

400

1 2

Točan broj paramaetara

Višak parametara

Page 58: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

53

Razlika u vremenu poziva metoda postoji jer Metoda1 mora kopirati tri dodatna,

nepotrebna parametra svaki put kada se poziva.

4.7.4 Performanse parametara metode i registri 

U ovom odlomku nas zanima da li i na koji način redoslijed parametara utječe na

registraciju varijabli. Također nas zanima može li se iskoristiti u svrhu ubrzavanja

izvođenja programskog koda. Pokušati ću izmjeriti razlike između loše poredanih i dobro

poredanih parametara.

Polazimo od pretpostavke da će dobro poredani parametri ubrzati rad programa samo u

slučaju da te parametre često koristimo unutar metode.

Ovo je moguće jer pri prevođenu koda prevoditelj koristi optimizaciju koja se sastoji od

pozivanja metode koja se zove FASTCALL. Ova metoda stavlja prva dva parametra u

registar te na taj način korištenje istih postaje brže.

Napravit ćemo dvije metode. Prva metoda Prva2 će na 10.000.000 iteracija testirati prva

dva parametra na neku vrijednost. Druga metoda Zadnja2 će na isti broj iteracija testirati

dva posljednja parametra na neku vrijednost.

 static bool Prva2(int a, int b, int c, int d, int e, int f) { // Ova mtoda testira prva dva parametra unutar petlje. for (int i = 5; i < 10000000; i++)   {     if (a == i)     {       return true;     }     if (b == i)     { 

return true;     }   }   if (c == 1 || d == 1 || e == 1)   {     return true;   }   return false; }   

Page 59: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

54

static bool Zadnja2(int a, int b, int c, int d, int e, int f) { // Ova metoda testira zadnja dva parametra unutar petlje. for (int i = 5; i < 10000000; i++)   {     if (e == i)     {       return true;     }     if (f == i)     {       return true;     }   }   if (a == 1 || b == 1 || c == 1)   {     return true;   }   return false; }  Programska logika ovih metoda je potpuno nevažna. Treba samo obratiti pažnju da u prvoj

metodi testiramo prva dva parametra, a u drugoj metodi dva posljednja parametra. Kada

se metode prevedu u C# jeziku, njihovi parametri se pri pozivu metode stavljaju na stog.

Interno, metoda koristi parametre sa stoga. Zbog metode FASTCALL prva dva parametra

se stavljaju u registar. U strojnom jeziku, registri su ekstremno brza memorija, odnosno

keš. Primjer gdje se to koristi je iteratorska varijabla i koja se obično sprema u registar radi

bržih operacija nad njom. U našem primjeru zbog pristupa tim parametrima iz registara, a

ne sa stoga dobiveno je ubrzanje od gotovo 10%.

Slika 24 Utjecaj redoslijeda parametra na brzinu izvođenja metode

4.7.5 Preopterećenje nativnih metoda 

Postoji mogućnost preopterećenja nativnih metoda koje nekad možemo iskoristiti za

dobivanje performansi. To se obavlja pomoću metode Equals. Metoda Equals se nalazi

96

105

90

95

100

105

110

1 2

Prva dva

Zadnja dva

Page 60: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

55

unutar System.Object. Da bi se koristila standardna implementacija metode Equals, naša

vrijednost mora biti zapakirana i predana kao instanca tipa System.ValueType. Potom

metoda Equals koristi refleksiju da bi obavila uspoređivanje. Iz ovoga se može zaključiti

da bi pretvorba između različitih tipova podataka te korištenje refleksije lako moglo biti

sporije od naše vlastite implementacije koja radi s točno određenim tipom podataka. Kao

rezultat toga, implementacija metode za naš specifični tip podatka može biti brža.

public struct Rectangle{   public double Length;   public double Breadth;   public override bool Equals(object ob){     if (ob isRectangle)       return Equals((Rectangle)ob);     else       returnfalse;   }   private bool Equals(Rectangle rect){     return  this.Length  ==  rect.Length  &&this.Breadth  == rect.Breadth;   } } 

4.8 Dretve 

Ovo potpoglavlje bavi se povećavanjem efikasnosti koda koji se izvršava u dretvama.

Dretve su oblik paralelizacije izvođenja programskog koda na razini procesa. Svaki se

proces može podijeliti u određen dretvi koje se izvršavaju prividno paralelno. U ovom

potpoglavlju će se obraditi slijedeći elementi koji utječu na optimizaciju programskog

koda:

• Smanjiti kreiranje dretvi.

• Koristiti ThreadPool klasu kada je potrebna dretva.

• Koristiti paralelne, radije nego sinkrone obrade.

4.8.1 Smanjiti stvaranje dretvi 

Dretve koriste upravljane (engl. managed) i neupravljane(engl. unmanaged) resurse te su

zbog toga skupe tijekom inicijalizacije [3]. Ako se dretve često kreiraju to može uzrokovat

usko grlo na procesoru koji cijelo vrijeme izmjenjuje dretve. Slijedeći odlomak

programskog koda prikazuje stvaranje i održavanje nove dretve u svakoj iteraciji. Rezultat

Page 61: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

56

toga je puno procesorskog vremena utrošenog na izmjene dretvi. Također,

GrabageCollector se stavlja pod pritisak jer treba počistiti sve nepotrebne resurse. Ovo je

način kako ne bi trebalo inicijalizirati dretve:

for (int i = 0; i < m; i++){   // Kreiraj i pokreni dretvu  

Thread th = newThread(newParameterizedThreadStart(MojaFunkcija)); 

  th.Start(i); } 

Trajanje kreiranja dretvi na ovaj način će biti jako dugo. U slijedećem primjeru se pokazati

kako korištenje klase ThreadPool može smanjiti to vrijeme.

4.8.2 ThreadPool klasa 

ThreadPool klasa se koristi kada želimo izbjeći skupu inicijalizaciju dretvi. Slijedeći

odlomak programskog koda ilustrira pokretanje dretvi pomoću te klase.

for (int i = 0; i < m; i++){   ThreadPool.QueueUserWorkItem(newWaitCallback(MojaFunkcija), i); } 

Nakon što se pozove metoda QueueUserWorkItems, metoda se stavlja u red za izvršavanje,

a trenutna dretva nastavlja s radom. ThreadPool klasa koristi dretve iz aplikacijskog skupa

dretvi kako bi izvršila metodu koja joj je poslana.

Slika 25 Utjecaj korištenja ThreadPool klase pri inicijalizaciji dretvi

Ovo se događa odmah nakon što prva dretva postane dostupna. Dok smo prije morali

eksplicitno kreirati svaku dretvu, u ovom slučaju koristimo već kreirane dretve u sistemu i

4

8392

0

2000

4000

6000

8000

10000

ThreadPool

Threading

Page 62: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

57

prosljeđujemo im posao. Razlika u performansama je ogromna i inicijalizacija korištenjem

ThreadPool klase je čak 2.000 puta brža.

4.8.3 Paralelni poslovi 

Prije nego se implementira asinkroni programski kod, dobro je razmotriti utjecaj

paralelnog izvođenja više poslova na ukupne performanse sustava. Povećavanje

paralelizma može imati golemi utjecaj na performanse programa. Kao što smo vidjeli,

dodatne dretve konzumiraju sistemske resurse poput procesora, memorije, diska i mreže.

Pri donošenju te odluke važno je vidjeti da li vam nove dretve pomažu ili odmažu u

performansama.

Korištenje paralelnih procesa najbolje dolaze do izražaja u situacijama gdje jedan proces

nije ovisan o rezultatu drugog procesa na način da ne treba čekati na njegov završetak i

implementirati posebnu sinkronizaciju. Ako proces koristi I/O operacije, on ima koristi od

vlastite dretve jer dretva može pauzirat dok druga dretva koristi isti resurs. No, ako je

posao usko vezan uz intenzivno korištenje procesora, onda će paralelno izvršavanje imati

nepovoljan učinak na performanse.

4.9 Obrada iznimki 

Strukturirano baratanje iznimkama korištenjem try/catch blokova je preporučeno pri izradi

robusnih aplikacija. Svakako bi u tom slučaju trebalo koristiti i finally blok kako bi bili

sigurni da su resursi oslobođeni i zatvoreni čak i u slučaju iznimke.

Iako je baratanje sa iznimkama preporučeno pri objektno orijentiranom razvoju, to ipak

nije tako jeftino u pogledu resursa kao što bi se na prvi pogled moglo pomisliti. Bacanje i

hvatanje iznimki je izuzetno skupa operacija [3]. Zbog toga se preporučuje da se iznimke

bacaju i hvataju samo onda kada je to stvarno potrebno, a ne da bi se njima kontrolirala

logika programa.

Evo nekih natuknica o kojima treba razmisliti kako bi se iznimke na ispravan način

koristile u programu:

• Ne koristiti iznimke da bi kontrolirali tok programa.

• „Bolje spriječiti nego liječiti.“ Koristiti validacije umjesto iznimki gdje god je to

moguće.

Page 63: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

58

• Višestruko bacanje iznimki je izuzetno skupo.

4.9.1 Ne koristiti iznimke da bi kontrolirali tok programa 

Bacanje iznimki je skupo. Zbog toga nije dobro koristiti iznimke da bi kontrolirali tok

programa. Ako možete očekivati seriju događaja koja će se vrlo vjerojatno dogoditi

tijekom rada vaše aplikacije, možda ne treba uopće bacati iznimke.

Slijedeći odlomak koda vraća logičku vrijednost true ako uspije naći broj u kolekciji. U

slučaju da ne vrati ništa onda baca iznimku koja se propagira i hvata.

for (int i = 0; i < m; i++){   if (polje[i] == broj)     return true; } Throw new Exception("Broj nije pronadjen"); 

Ovo ponašanje je očekivano sa stajališta programske logike. Ako imamo kolekciju brojeva

i tražimo jedan broj u njoj, onda postoji mogućnost da nećemo naći traženi broj. Slijedeći

odlomak koda vraća vrijednost false u slučaju da ne može naći broj u kolekciji.

for(int i=0;i<m;i++){   if (polje[i] == broj)     return true; } return false; 

Vraćanje informacije o grešci putem neke vrijednosti je kritično za performanse.

Izbjegavanjem korištenje iznimaka tamo gdje stvarno nisu potrebne je uobičajena tehnika

kojom se povećavaju performanse objektno-orijentiranih aplikacija. Naravno to ne znači da

u programu treba izbjegavati ispitivanje grešaka i nenormalnog ponašanja, već ih samo

obrađivati na onaj način koji je za danu namjenu optimalan sa stajališta performansi. Na

slici 26 je vidljivo da se korištenjem ove tehnike mogu postići gotovo dvostruko bolji

rezultati.

Page 64: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

59

Slika 26 Utjecaj korištenja iznimki pri otkrivanju grešaka na performanse programa

4.9.2 Koristiti validacije umjesto iznimki 

Ako znate da možete izbjeći određeni tip greške pisanjem programskog koda koji

provjerava i sprječava mogućnost njezinog nastanka, onda to svakako učinite. Primjerice,

ako dijelite s brojevima koje dobivate iz nekog izvora te postoji mogućnost da neki brojevi

nisu inicijalizirani, prvo provjerite da li su inicijalizirani prije nego dijelite s njima.

Slijedeći odlomak programskog koda pokazuje koja je razlika između validacije i try/catch

bloka.

Try catch:

try{   return djeljenik / djelitelj; } catch(Exception e){   return System.Double.NaN; }  Validacija:

if (dijeljitelj != 0) {   return djeljenik / djelitelj; } return System.Double.NaN; 

Rezultati mjerenja gdje se validacijama sprječava nastanak greške i izbacivanje iznimki

ovisi o učestalosti nastanka greške koju sprječavamo. U našem primjeru čiji rezultati su

prikazani na slici 27, na slučajno izabranom skupu podataka performanse su gotovo pet

puta bolje u slučaju kada je programski kod sprječavao nastanak greške u odnosu na kod

koji je po nastanku grešku registrirao korištenjem iznimaka.

9

21

0

5

10

15

20

25

1 2

Bez iznimke

S iznimkom

Page 65: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

60

Slika 27 Utjecaj sprječavanja grešaka na povećanje performansi programa

4.9.3 Ponovno bacanje iznimaka je skupo 

Trošak ponovnog bacanja iznimke je gotovo jednak kao i stvaranje nove iznimke. Slijedeći

odlomak koda prikazuje hvatanje i ponovno bacanje iznimke.

try{   //akcija koja uzrokuje bacanje iznimke... } catch(Exception e){   //rad nad iznimkom i njeno ponovno bacanje   throw; }  U pravilu se iznimke ponovno bacaju samo onda tu iznimku želite propagirati klasama

nadređene razine te tim putem programu odnosno korisniku dati više informacija o samoj

grešci.

46

258

0

50

100

150

200

250

300

1 2

S validacijom

Bez validacije

Page 66: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

61

5 ZAKLJUČAK 

Performanse čine samo djelić kvalitete cijele aplikacije te obično nisu ni toliko važne. Uz

to, optimizirani kod čini samo djelić performansi te obično nije najvažniji. Dobra

arhitektura aplikacije, detaljan dizajn ili dobra struktura podataka će imati veći utjecaj na

cjelokupne performanse sustava. Sve prethodno navedeno spada u elemente kojima se bave

različite strategije optimizacije. To je nešto što bi se trebalo obaviti prije nego što se

bacimo na optimiziranje programskog koda. Također bi trebalo razmisliti i o poboljšanju

strojne opreme pošto njezine cijene stalno padaju te se nekada nabavkom nove opreme

može postići znatno poboljšanje performansi sustava uz najmanji mogući trošak.

Iz opisanih tehnika optimizacije je vidljivo koja su područja najizloženija neefikasnom

kodu. Najveći dio rada bavi se opisom tih mjesta te sam na njih utrošio i najviše vremena

pišući i testirajući kod. Iako na prvi pogled izgleda da se pravila odnosno preporuke

optimizacije mogu vrlo jednostavno i jasno preslikati u konkretan programski kod, to u

praksi baš i nije uvijek tako. Optimizacija koda je škakljiv posao koji zahtjeva puno

priprema. Kod optimizacije je vrlo važno prvo identificirati neefikasan kod, zatim ga

optimizirati te naposljetku izmjeriti ostvarene promjene. Moglo bi se reći da je testiranje i

mjerenje najvažniji dio optimizacije jer bez njih optimizacija baš i nema puno smisla.

Iako je izrada ovog dokumenta u nekim trenutcima bila naporna, uživao sam otkrivati nove

načine optimizacije koji su unaprijedili moje programerske sposobnosti. Moram također

priznati da sam neke stavke iz ovoga rada već primijenio u svojem svakodnevnom poslu te

se nadam da će ovo istraživanje pomoći i drugima.

Page 67: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

62

6 LITERATURA 

[1] Sam Allen, „Dot Net Perls“, http://www.dotnetperls.com (20.10.2011.)

[2] Kalid Azad, „Understanding the Pareto Principle (The 80/20 Rule) “,

http://betterexplained.com/articles/understanding-the-pareto-principle-the-8020-rule/

(20.10.2011.)

[3] Rico Mariani, Brandon Bohling, Connie U. Smith, Scott Barber, „Improving .NET

Application Performance and Scalability“, Microsoft Press, 2004.

[4] Steve McConnell, „Code Complete“, Microsoft Press, 2004.

[5] Voltaire, Voltaire's Philosophical Dictionary, Carlton House, New York, 1900,

http://www.archive.org/stream/voltairesphiloso18569gut/18569.txt (20.10.2011.)

[6] William A. Wulf, „A Case Against the GOTO“, Proceedings of the ACM annual

conference ACM '72—Volume 2, ACM, New York, USA, 1972.

Page 68: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

63

7 SAŽETAK 

Ovaj rad daje odgovore na neka uobičajena pitanja u optimizaciji. Glavne točke rada su

strategije i tehnike optimizacije. Opisane su strategije optimizacije, generalno je objašnjena

optimizacija i njezine temeljne značajke s naglaskom na performanse i njihovo različito

tumačenje. Pokazuju se koraci koje treba poduzeti prije optimizacije koda da bi se izbjegao

negativan utjecaj optimiziranja na ispravnost i čitljivost koda. Također se opisuju mjerenja

kao jedinog sredstva prepoznavanja poboljšanja performansi. Pojašnjeni su i neki

uobičajeni izvori uskih grla te kako ih izbjeći. Tehnike optimizacije na najnižoj razini su

praktično ilustrirane primjerima programskog koda u C# programskom jeziku koji

prikazuju ostvarene rezultate optimizacije. Tehnike optimizacije će obraditi najučestalije

slučajeve na koje programer može naići poput optimizacije nizova, petlji, tipova podataka i

metoda.

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

KLJUČNE RIJEČI:

Optimizacija, strategije optimizacije, tehnike optimizacije, proces optimizacije, C#

Page 69: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

64

8 ABSTRACT 

This thesis answers some common optimization questions. Main focus points of this paper

are optimization strategies and techniques. Fundamental characteristics of optimization

strategies are explained in general with focus on performances and their different

interpretations. Important steps that developer has to take in this process, in order to avoid

negative impact of optimization on correctness and readability of code are shown.

Measurement, as the only instrument in recognizing performance improvement is

described. Common sources of bottlenecks and how to avoid them are also explained.

Optimization techniques on low level that practically and visually show dominance of

optimized code over unoptimized are illustrated with examples in C# programming

language. Optimization techniques handle the most common fields where programmer can

bump into, such as optimization of strings, loops, data types and methods.

SOURCE CODE OPTIMIZATION STRATEGIES AND TECHNIQUES

KEYWORDS:

Optimization, optimization strategies, optimization technics, optimization process, C#

Page 70: diplomski rad strategije i tehnike optimizacije programskog koda

STRATEGIJE I TEHNIKE OPTIMIZACIJE PROGRAMSKOG KODA

65

9 PRILOZI 

9.1 Popis slika 

Slika 1 Ciklus odnosa optimizacije i snage računala ............................................................. 3 

Slika 2 Odnos duljine programskog koda i brzine izvođenja .............................................. 12 

Slika 3 Odnos brzine memorije i čvrstog diska ................................................................... 16 

Slika 4 Ciklus optimizacije programskog koda ................................................................... 20 

Slika 5 Zaustavljanje ispitivanja kada je odgovor poznat ................................................... 24 

Slika 6 Odnos brzina različitih metoda pri spajanju više nizova ......................................... 30 

Slika 7 Ispravna upotreba metode Append() kod rada s pojedinačnim znakovima ............. 31 

Slika 8 Podrška od strane VisualStudia pri izradi ekstenzijskih(engl. extension) metoda .. 33 

Slika 9 Razlika u performansama nativne ToString() metode i naše implementacije ......... 33 

Slika 10 Pokazatelj skupoće korištenja ToString() metode ................................................. 35 

Slika 11 Razlika u brzini for i foreach petlji ....................................................................... 36 

Slika 12 Prikaz specifičnosti x86 arhitekture na performanse kretanja kroz petlju ............ 37 

Slika 13 Spajanje petlji kao tehnika optimizacije ................................................................ 38 

Slika 14 Odmotavanje petlji kao tehnika optimizacije ........................................................ 39 

Slika 15 Unswitching kao tehnika optimizacije. ................................................................. 40 

Slika 16 Izbacivanje posla iz petlje. .................................................................................... 41 

Slika 17 Izbjegavanje pakiranja(engl. boxing). .................................................................. 43 

Slika 18 Spljoštivanje polja kao tehnika optimizacije. ........................................................ 44 

Slika 19 Prikaz skupoće korištenja izraza na primjeru negacije ........................................ 47 

Slika 20 Povećanje performansi predkalkuliranjem vrijednosti i korištenjem konstanti .... 48 

Slika 21 Performanse pri korištenju inline metoda ............................................................. 51 

Slika 22 Dominacija statičnih metoda pri mjerenju brzine poziva ...................................... 52 

Slika 23 Utjecaj smanjivanja broja parametara na brzinu poziva metode ........................... 52 

Slika 24 Utjecaj redoslijeda parametra na brzinu izvođenja metode................................... 54 

Slika 25 Utjecaj korištenja ThreadPool klase pri inicijalizaciji dretvi ................................ 56 

Slika 26 Utjecaj korištenja iznimki pri otkrivanju grešaka na performanse programa ....... 59 

Slika 27 Utjecaj sprječavanja grešaka na povećanje performansi programa ....................... 60