Optimizarea este o tehnică de transformare a programului, care încearcă să îmbunătățească codul făcându-l să consume mai puține resurse (adică CPU, memorie) și să livreze viteză mare.
în optimizare, construcțiile generale de programare la nivel înalt sunt înlocuite cu coduri de programare la nivel scăzut foarte eficiente. Un proces de optimizare a codului trebuie să respecte cele trei reguli de mai jos:
-
codul de ieșire nu trebuie, în niciun fel, să schimbe sensul programului.
-
optimizarea ar trebui să crească viteza programului și, dacă este posibil, programul ar trebui să solicite un număr mai mic de resurse.
-
optimizarea ar trebui să fie ea însăși rapidă și nu ar trebui să întârzie procesul general de compilare.
eforturile pentru un cod optimizat pot fi făcute la diferite niveluri de compilare a procesului.
-
la început, utilizatorii pot schimba/rearanja codul sau pot folosi algoritmi mai buni pentru a scrie codul.
-
după generarea codului intermediar, compilatorul poate modifica codul intermediar prin calcule de adrese și îmbunătățirea buclelor.
-
în timp ce produce codul mașinii țintă, compilatorul poate folosi ierarhia memoriei și registrele CPU.
optimizarea poate fi clasificată în linii mari în două tipuri : independent de mașină și dependent de mașină.
optimizare independentă de mașină
în această optimizare, compilatorul preia codul intermediar și transformă o parte a codului care nu implică registre CPU și/sau locații de memorie absolută. De exemplu:
do{ item = 10; value = value + item; } while(valueThis code involves repeated assignment of the identifier item, which if we put this way:
Item = 10;do{ value = value + item; } while(valueshould not only save the CPU cycles, but can be used on any processor.
Machine-dependent Optimization
Machine-dependent optimization is done after the target code has been generated and when the code is transformed according to the target machine architecture. It involves CPU registers and may have absolute memory references rather than relative references. Machine-dependent optimizers put efforts to take maximum advantage of memory hierarchy.
Basic Blocks
Source codes generally have a number of instructions, which are always executed in sequence and are considered as the basic blocks of the code. These basic blocks do not have any jump statements among them, i.e., when the first instruction is executed, all the instructions in the same basic block will be executed in their sequence of appearance without losing the flow control of the program.
A program can have various constructs as basic blocks, like IF-THEN-ELSE, SWITCH-CASE conditional statements and loops such as DO-WHILE, FOR, and REPEAT-UNTIL, etc.
Basic block identification
We may use the following algorithm to find the basic blocks in a program:
-
declarații antet căutare de toate blocurile de bază de unde începe un bloc de bază:
- prima declarație a unui program.
- declarații care sunt țintă de orice ramură (condiționată/necondiționată).
- declarații care urmează orice declarație de ramură.
-
declarațiile antet și declarațiile care le urmează formează un bloc de bază.
-
un bloc de bază nu include nici o declarație antet de orice alt bloc de bază.
blocurile de bază sunt concepte importante atât din punct de vedere al generării de cod, cât și al optimizării.
blocuri de bază joacă un rol important în identificarea variabilelor, care sunt utilizate de mai multe ori într-un singur bloc de bază. Dacă o variabilă este utilizată de mai multe ori, memoria de registru alocată acelei variabile nu trebuie golită decât dacă blocul termină execuția.
graficul fluxului de Control
blocurile de bază dintr-un program pot fi reprezentate prin intermediul graficelor fluxului de control. Un grafic de flux de control descrie modul în care controlul programului este trecut printre blocuri. Este un instrument util care ajută la optimizarea de ajutor localizarea orice bucle nedorite în program.
optimizarea buclei
majoritatea programelor rulează ca o buclă în sistem. Este necesară optimizarea buclelor pentru a salva ciclurile procesorului și memoria. Buclele pot fi optimizate prin următoarele tehnici:
-
Cod Invariant: un fragment de cod care se află în buclă și calculează aceeași valoare la fiecare iterație se numește cod invariant în buclă. Acest cod poate fi mutat din buclă salvându-l pentru a fi calculat o singură dată, mai degrabă decât cu fiecare iterație.
-
analiza inducției: o variabilă se numește variabilă de inducție dacă valoarea sa este modificată în buclă printr-o valoare invariantă în buclă.
-
reducerea forței: există expresii care consumă mai multe cicluri CPU, timp și memorie. Aceste expresii ar trebui înlocuite cu expresii mai ieftine, fără a compromite rezultatul expresiei. De exemplu, multiplicarea (x * 2) este costisitoare în ceea ce privește ciclurile procesorului decât (x
cod mort eliminare
cod mort este una sau mai multe declarații de cod, care sunt:
- fie niciodată executat sau inaccesibil,
- sau dacă este executat, ieșirea lor nu este niciodată utilizată.
astfel, Codul mort nu joacă niciun rol în nicio operație de program și, prin urmare, poate fi pur și simplu eliminat.
Cod parțial mort
există câteva declarații de cod ale căror valori calculate sunt utilizate numai în anumite circumstanțe, adică uneori valorile sunt utilizate și alteori nu. Astfel de coduri sunt cunoscute sub numele de cod parțial mort.
graficul fluxului de control de mai sus descrie o bucată de program în care variabila ‘A’ este utilizată pentru a atribui ieșirea expresiei ‘x * y’. Să presupunem că valoarea atribuită lui ‘ a ‘ nu este niciodată folosită în interiorul buclei.Imediat după ce controlul părăsește bucla, lui ‘a’ i se atribuie valoarea variabilei ‘z’, care va fi utilizată ulterior în program. Concluzionăm aici că codul de atribuire al ‘ a ‘ nu este niciodată folosit nicăieri, prin urmare este eligibil pentru a fi eliminat.
de asemenea, imaginea de mai sus descrie că declarația condiționată este întotdeauna falsă, ceea ce înseamnă că codul, scris în caz adevărat, nu va fi executat niciodată, prin urmare poate fi eliminat.
redundanță parțială
expresiile redundante sunt calculate de mai multe ori în cale paralelă, fără nicio modificare a operanzilor.în timp ce expresiile redundante parțiale sunt calculate de mai multe ori pe o cale, fără nicio modificare a operanzilor. De exemplu,
|
|
Codul invariant în buclă este parțial redundant și poate fi eliminat folosind o tehnică de mișcare a codului.
un alt exemplu de cod parțial redundant poate fi:
If (condition){ a = y OP z;}else{ ...}c = y OP z;
presupunem că valorile operanzilor (y și z) nu sunt modificate de la atribuirea variabilei a la variabila c. aici, dacă afirmația condiție este adevărată, atunci y OP z este calculat de două ori, altfel o dată. Code motion poate fi folosit pentru a elimina această redundanță, așa cum se arată mai jos:
If (condition){ ... tmp = y OP z; a = tmp; ...}else{ ... tmp = y OP z;}c = tmp;
aici, dacă condiția este adevărată sau falsă; y OP z ar trebui calculat o singură dată.