Optimizarea codului
- definiție și proprietăți
- tipuri și niveluri de optimizare
- ce să optimizăm
- rezultate bune și rele ale optimizării
- concluzie
- PVS-Studio
cuprins
definiție și proprietăți
optimizarea codului este orice metodă de modificare a codului pentru a îmbunătăți calitatea și eficiența Codului. Un program poate fi optimizat astfel încât să devină o dimensiune mai mică, să consume mai puțină memorie, să execute mai rapid sau să efectueze mai puține operații de intrare/ieșire.
cerințele de bază metodele de optimizare ar trebui să respecte, este că un program optimizat trebuie să aibă aceeași ieșire și efecte secundare ca versiunea sa non-optimizat. Cu toate acestea, această cerință poate fi ignorată în cazul în care beneficiul optimizării este estimat a fi mai important decât consecințele probabile ale unei modificări a comportamentului programului.
tipuri și niveluri de optimizare
optimizarea poate fi realizată de optimizatori automate, sau programatori. Un optimizator este fie un instrument software specializat, fie o unitate încorporată a unui compilator (așa-numitul compilator de optimizare). Procesoarele moderne pot optimiza, de asemenea, ordinea de execuție a instrucțiunilor de cod.
optimizările sunt clasificate în optimizări de nivel înalt și de nivel scăzut. Optimizările la nivel înalt sunt de obicei efectuate de programator, care gestionează entități abstracte (funcții, proceduri, clase etc.) și ține cont de Cadrul general al sarcinii de optimizare a proiectării unui sistem. Optimizări efectuate la nivelul blocurilor structurale elementare ale codului sursă – bucle, ramuri etc. -sunt de obicei denumite optimizări la nivel înalt, în timp ce unii autori le clasifică într-un nivel separat (“mediu”) (N. Wirth?). Optimizările de nivel scăzut sunt efectuate în etapa în care codul sursă este compilat într-un set de instrucțiuni ale mașinii și în acest stadiu se utilizează de obicei optimizarea automată. Programatorii de asamblare cred totuși că nici o mașină, oricât de perfectă, nu poate face acest lucru mai bine decât un programator priceput (totuși toată lumea este de acord că un programator sărac va face mult mai rău decât un computer).
ce să optimizăm
cu optimizarea manuală a codului, se confruntă cu o altă problemă: nu trebuie doar să știm cum ar trebui făcută exact optimizarea, ci și ce parte specială a programului ar trebui optimizată. Din diverse motive (operații de intrare lente, diferența de viteză de lucru a unui operator uman și a unui computer etc.), 90% din timpul de execuție al unui program este cheltuit executând doar 10% din Cod (această afirmație este mai degrabă speculativă, principiul Pareto fiind un motiv destul de îndoielnic, dar A. Tanenbaum îl face să pară convingător). Deoarece optimizarea necesită timp suplimentar în afară de timpul pe care l-ați petrecut pentru dezvoltarea programului, ar fi bine să vă concentrați pe optimizarea acestui cod critic de 10%, mai degrabă decât să încercați să optimizați întregul program. Aceste fragmente de cod sunt cunoscute sub numele de blocaje și pot fi detectate de utilități speciale – profilatori – care pot măsura timpul necesar pentru executarea diferitelor părți ale programului.
în practică, însă, optimizarea se face de obicei după etapa programării “haotice” (inclusiv metode precum “Copy-Paste”, “vom vedea mai târziu”, “este în regulă în acest fel”) și, prin urmare, este un amestec de optimizare ca atare, refactorizare și remedieri de erori: simplificarea constructelor “queer” precum strlen(path.c_str ()), condiții logice precum (a.x != 0 && a.X != 0), și așa mai departe. Profilerii sunt de puțin ajutor cu acest tip de optimizare. Cu toate acestea, puteți detecta aceste probleme cu instrumente de analiză statică, adică instrumente concepute pentru a căuta erori semantice, bazându-se pe analiza profundă a codului sursă. După cum puteți vedea din exemplul menționat mai sus cu condiția ciudată, codul ineficient poate apărea ca urmare a erorilor (ca o greșeală de tipar în exemplul nostru, unde a.X != 0 && a.y != 0 ar trebui să fie în schimb). Un analizor static puternic va detecta astfel de fragmente de cod și vă va atrage atenția asupra acestora prin producerea de mesaje de avertizare.
rezultate bune și rele ale optimizării
în programare, aproape totul ar trebui tratat din punctul de vedere al raționalității – optimizarea nu face excepție. Există o credință că codul scris de un programator de asamblare neexperimentat este de 3-5 ori mai lent decât codul generat de compilator (Zubkov). Cunoscută pe scară largă este o frază a lui Knuth cu privire la optimizările timpurii la nivel scăzut (cum ar fi încercările de salvare a operatorilor sau variabilelor): “optimizarea prematură este rădăcina tuturor relelor”.
majoritatea programatorilor nu se plâng de optimizările efectuate de optimizator, dintre care unele sunt convenționale și obligatorii. Cum ar fi, de exemplu, optimizarea apelurilor de coadă în limbaje funcționale (apelul de coadă este un caz special de recursivitate, care poate fi reprezentat ca o buclă).
cu toate acestea, ar trebui să înțelegem că mai multe optimizări complexe la nivelul codului mașinii pot provoca o încetinire mare a compilării. Beneficiul pe care vă permit să îl câștigați poate fi mult prea nesemnificativ, în comparație cu optimizările generale de proiectare a sistemului (Wirth). De asemenea, trebuie să rețineți că limbile moderne, cu toate “bibelourile” lor sintactice și semantice, au multe nuanțe și subtilități, astfel încât un programator care nu este familiarizat cu ele poate fi surprins de un rezultat al optimizării.
de exemplu, luați C++ și așa-numita optimizare a valorii returnate, atunci când compilatorul evită copierea unui obiect temporar returnat de o funcție. Deoarece compilatorul omite copierea, această metodă se mai numește și “copiere eliziune”. Deci, următorul cod:
#include <iostream> struct C { C() {} C(const C&) { std::cout << "A copy was made.\n"; }}; C f() { return C();} int main() { std::cout << "Hello World!\n"; C obj = f();}
poate avea mai multe ieșiri:
Hello World!A copy was made.A copy was made.Hello World!A copy was made.Hello World!
oricât de ciudat ar părea, toate cele trei versiuni sunt valabile, deoarece standardul lingvistic permite omiterea apelurilor unui constructor de copiere în astfel de cazuri, chiar dacă constructorul are efecte secundare (articolul 12.8 din categoria Obiecte de copiere, punctul 15).
concluzie
astfel, ar trebui să luăm în considerare întotdeauna optimizarea codului programului folosind utilități specializate ori de câte ori este posibil, dar faceți acest lucru cu multă grijă și fiți pregătiți pentru probabilitatea unor trucuri neașteptate de la compilator uneori.
PVS-Studio
un set de diagnostice este implementat în analizorul static PVS-Studio care vă permite să găsiți unele situații în care codul poate fi optimizat. Cu toate acestea, PVS-Studio ca orice alt Analizor static nu poate servi ca înlocuitor al instrumentelor de profilare. Numai analizoarele de programe dinamice sunt capabile să identifice blocajele. Analizoarele statice nu știu ce programe de date de intrare primesc și cât de des este executată o anumită bucată de cod. De aceea spunem că analizorul sugerează implementarea unor “micro-optimizări” de cod, care nu garantează câștigurile de performanță.
în ciuda dezavantajului considerat, PVS-Studio analyzer acționează ca o completare bună a instrumentelor de profilare. Mai mult, atunci când se ocupă de avertismentele PVS-Studio, legate de optimizare, codul devine adesea mai simplu și mai scurt. Acest efect este considerat mai detaliat în articolul “explorarea Microoptimizărilor folosind codul Tizen ca exemplu”.