Misconcepţii despre tuningul de performanţă

by adrian.tosca 24. July 2008 10:16

„Imbunătaţirea performanţei se face prin scrierea de cod rapid”

Această părere este foarte populară printre programatori dar se dovedeşte falsă în majoritatea cazurilor. Cele mai dramatice imbunătăţiri de performanţă se pot face din faza de proiectare iar mici modificări de arhitectură poat face diferenţa între un program atât de lent că e de nefolosit si unul acceptabil. Modificările la nivel de cod au de cele mai multe ori efecte locale şi chiar dacă se alege această soluţie, cele mai mari imbunătăţiri se obţin prin schimbarea algoritmilor si stucturilor de date folosite.

„Performanţa este o cerinţă funcţională”

Performanţa este specificată ca cerinţă funcţională de foarte multe ori fară a fi neaparăt necesară. Am citit despre un sistem ale cărui cerinţe specificau foarte clar că timpul de răspuns pentru utilizatorul final trebuia să fie mai mic de 1 secundă [1]. După terminarea analizei, s-a făcut o propunere de arhitectură estimată la aproximativ 100 milioane $. S-a considerat că sistemul este mult prea scump şi s-au căutat alternative. Dupa o analiză s-a ajuns la concluzia că utilizatorii finali pot folosi foarte bine sistemul şi cu un timp de raspuns < 3 secunde. După refacerea arhitecturii, costul estimat s-a redus la 30 milioane $.

Înainte de a investi timp pentru a rezolva problemele de performanţă asigură-te că rezolvi o problemă care chiar trebuie rezolvată.

„Reducerea numărului de linii de cod îmbunătaţeşte viteza”

Mulţi programatori cred cu tenacitate că dacă scriu secvenţe de cod în 1 sau 2 linii fac codul cât se poate de eficient. Un caz oarecum limită poate arăta dacă asta este sau nu adevărat.

Să considerăm următorul cod care iniţializează un array cu 10 elemete :

  for (int i = 0; i != 10; i++)
    a[i] = i;

Codul va fi mai rapid sau mai încet decât următoarele 10 linii de cod care fac acelaşi lucru?

  a[0] = 0;
  a[1] = 1;
  ...
  a[9] = 9;

Dacă se urmăreşte principiul cu "mai puţine linii înseamnă cod mai rapid" se concluzionează că primul fragment de cod e mai rapid. Pentru c# testele arată însă că al doilea fragment este mai rapid cu aproximativ 8%. În cazul altor limbaje de programare diferenţele sunt chiar mai semnificative[2].

Bineînţeles asta nu înseamnă că creşterea numărului de linii de cod îmbunătaţeşte viteza. Înseamnă doar că nu există o relaţie predictibilă între numărul de linii de cod şi viteza de rulare.

„Probabil că anumite operaţii sunt mai rapide decât altele”

Nu există noţiunea de probabilitate atunci când se vorbeşte de performantă. Performanţa trebuie întodeauna măsurată pentru a ştii dacă modificările au îmbunătaţit sau nu viteza de execuţie. Regulile se schimba de fiecare dată când se schimbă limbajul de programare, compilatorul, versiunea compilatorului, librăriile folosite, versiunile librăriilor, procesorul, cantitatea de memorie, sistemul de operare, culoarea tricoului pe care îl porţi (OK, ăsta nu) şi aşa mai departe[2]. Ceea ce e adevărat pe o maşină cu un anumit set de librării poate fi fals pe altă maşină cu alt set de librării.

Acest comportament sugerează mai multe motive pentru a nu îmbunatăţii performanţa prin tuning de cod. Tehnicile care îmbunătaţesc performanţa într-un anumit mediu o pot degrada în alt mediu. Câteodata modificările de cod „deştepte” pot anula optimizări ale compilatorului care au fost gândite să funcţioneze pe cod simplu si clar.

„Optimizările trebuie făcute pe parcurs”

Există o teorie care spune că dacă te chinui să scrii cod rapid si eficient pe parcurs ce scrii fiecare rutină, programul per ansamblu va fi mai rapid şi mai eficient. Această abordare creeaza însă o situaţie de tip „pădurea care nu se vede de copaci” în care programatorii ignoră optimizări globale semnificative pentru că sunt prea ocupaţi cu micro-optimizările.

Există câteva probleme asociate cu optimizările făcute pe parcurs:

a) Este imposibil să se identifice gâtuirile[3] de performanţă înainte ca programul să fie complet. Există mai multe studii care arată ca majoritatea timpului de execuţie al unei aplicaţii se consumă intr-o zonă restrânsă de cod. Un studiu clasic arată ca aproximativ 50% din timpul de execuţie al unei aplicaţii se consumă în 4% din cod [4]. În acest studiu Knuth a reuşit să imbunătăţească performanţa unei aplicaţii (chiar programul de profiling pe care îl folosea) cu 50% prin schimbarea câtorva linii de cod în doar două rutine. Asta are ca implicaţie că trebuie căutate şi optimizate acele rutine care consumă cel mai mult timp. Programatorii ghicesc foarte rar care anume procent de 4% consuma cel mai mult timp de execuţie, deci programatorii care optimizează pe parcurs or sa petreacă în medie cu 96% mai mult timp pentru a optimiza cod care nu are nevoie de optimizări.

b) În rarele cazuri in care un programator indentifică corect locurile cu probleme de performanţă se fac prea multe modificări locale care pot afecta performanţa sistemului pe ansamblu. Optimizările făcute dupa ce s-a terminat implementarea pot identifica mai bine zonele cu probleme în tot sistemul precum şi importanţele relative între ele. Optimizărilor făcute pe parcurs suferă din cauza lipsei de perspectivă.

c) Focalizarea pe optimizare in faza de dezvoltare diminuează atenţia de la alte obiective importante. Apar dezbateri şi analizele de algoritmi în faze incipiente care nu aduc prea mare valoare pentru utilizatorul final. Mult mai importante în aceste faze sunt corectitudinea şi usurinţa de citire a codului iar acestea sunt mult mai greu de îmbunatăţit mai tarziu comparativ cu performanţa. Principala problemă cu care trebuie să se confrunte programatorii este complexitatea şi controlării acesteia trebuie să îi fie dedicată majoritatea timpului de dezvoltare. Ce ai face mai degraba într-o fază finală? Muncă pentru imbunătăţirea performanţei pe 4% din aplicaţie sau muncă pentru imbunătăţirea calităţii codului pe 100% din aplicaţie?

„Un program rapid e la fel de important ca unul corect”

Niciodată nu este adevarat că este mai important ca un program să fie rapid înainte de a fi corect. Următoarea relatare anectodică exemplifică foarte bine ideea.

Un programator senior care lucra pe post de consultant, a făcut o deplasare la o firmă pentru a rezolva o problemă cu care se confruntau cei de acolo. După o lungă discuţie însă, s-a ajuns la concluzia că rezolvarea este mult prea complicată si până la urmă s-a decis închiderea proiectului. A doua zi însă, consultantului i-a venit o idee simplă pentru a rezolva problema. S-a întors la firmă, a convins lumea să redeschidă proiectul şi a implementat soluţia respectivă. La discuţia care a urmat după terminarea implementării, unul din programatorii de la firmă a întrebat cât durează rularea programului în noua implementare.

— Cam 10 secunde pentru date de intrare obişnuite, răspunde consultantul.
— Hei, intervine din nou programatorul de la firmă, dar versiunea veche rula sub 1 secundă pentru acelaşi tip de date de intrare.
— Da, răspunde consultantul, dar versiunea veche nu funcţiona. Dacă programul meu nu trebuie să funcţioneze, pot sa îl fac să ruleze instant[2].

Pentru o anumită clasă de proiecte performanţa este foarte importantă, însă această clasă este mult mai mică decât cred cei mai mulţi. Pentru aceste proiecte performanţa trebuie planificată din primele faze printr-un design adecvat. Pentru celelalte tipuri de proiecte orice optimizare prematură este o ameninţare pentru calitatea generală, incluzand chiar perfomanţa.

----------
[1] Jon Louis Bentley, More Programming Pearls: Confessions of a Coder, 1988
[2] Steve McConnell, Code Complete 2nd edition, 2004
[3] Adica “bottlenecks” :)
[4] Donald Knuth, An empirical study of Fortran programs, 1971
[5] Edsger W. Dijkstra, The Humble Programmer, 1972

Tags: , ,

Performance & Scalability

Comments

Comments are closed

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen

RecentComments

Comment RSS

Calendar

<<  September 2010  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar