Optimizarea codului: modul optim de optimizare

Publicat: 2022-03-11

Optimizarea performanței este una dintre cele mai mari amenințări la adresa codului dvs.

S-ar putea să te gândești, nu încă unul dintre acești oameni . Înțeleg. Optimizarea de orice fel ar trebui să fie în mod clar un lucru bun, judecând după etimologia ei, așa că în mod firesc, vrei să fii bun la asta.

Nu doar pentru a te deosebi de mulțime ca un dezvoltator mai bun. Nu doar pentru a evita să fii „Dan” pe The Daily WTF , ci pentru că crezi că optimizarea codului este ceea ce trebuie făcut. Ești mândru de munca ta.

Hardware-ul computerului continuă să devină mai rapid și software-ul mai ușor de realizat, dar orice lucru simplu pe care doriți să îl puteți face, la naiba durează întotdeauna mai mult decât ultimul. Dai din cap la acest fenomen (de altfel, cunoscut sub numele de Legea lui Wirth) și te hotărăști să contrazici această tendință.

E nobil din partea ta, dar încetează.

Doar opreste!

Sunteți în cel mai mare pericol de a vă zădărnici propriile obiective, indiferent cât de experimentat ați avea la programare.

Cum așa? Să ne întoarcem.

În primul rând, ce este optimizarea codului?

Adesea, atunci când îl definim, presupunem că vrem ca codul să funcționeze mai bine. Spunem că optimizarea codului înseamnă scrierea sau rescrierea codului, astfel încât un program să folosească cel mai puțin spațiu posibil de memorie sau de pe disc, să-și minimizeze timpul CPU sau lățimea de bandă a rețelei sau să folosească cât mai bine nucleele suplimentare.

În practică, uneori adoptăm o altă definiție: Scrierea mai puțin cod.

Dar codul preventiv preventiv pe care îl scrii cu acest scop are și mai multe șanse să devină un ghimpe în coasta cuiva. A caror? Următoarea persoană ghinionistă care trebuie să-ți înțeleagă codul, care poate fi chiar și tu. Și cineva inteligent și capabil, ca tine, poate evita autosabotarea: menține-ți scopurile nobile, dar reevaluează-ți mijloacele, în ciuda faptului că par a fi incontestabil intuitive.

Cod Golf: +197%, Performanță: -398%, Simplitate: -9999%

Deci optimizarea codului este un termen puțin vag. Asta înainte de a lua în considerare unele dintre celelalte moduri prin care se poate optimiza codul, pe care le vom analiza mai jos.

Să începem prin a asculta sfaturile înțelepților în timp ce explorăm împreună celebrele reguli de optimizare a codului lui Jackson:

  1. Nu o face.
  2. (Numai pentru experți!) Nu o faceți încă .

1. Nu o face: canalizarea perfecționismului

O să încep cu un exemplu destul de jenant de extrem dintr-o perioadă în care, cu mult timp în urmă, tocmai mă udam picioarele în minunata lume a SQL-ului. Problema a fost ca am calcat apoi pe prajitura si nu am vrut sa o mai mananc pentru ca era uda si a inceput sa miroase a picioare.

Tocmai îmi udam picioarele în lumea minunată a SQL-ului. Problema a fost că apoi am călcat pe tort...

Aștepta. Lasă-mă să mă retrag din această epavă a unei metafore pe care tocmai am făcut-o și să explic.

Făceam R&D pentru o aplicație intranet, care speram să devină într-o zi un sistem de management complet integrat pentru mica afacere în care lucram. Ar urmari totul pentru ei și, spre deosebire de sistemul lor actual, nu și-ar pierde niciodată datele, deoarece ar fi susținut de un RDBMS, nu de fișierul plat produs de casă, pe care îl folosise alt dezvoltator. Am vrut să proiectez totul cât mai inteligent posibil încă de la început pentru că aveam o tablă goală. Ideile pentru acest sistem explodau ca artificii în mintea mea și am început să proiectez tabele - contacte și numeroasele lor variații contextuale pentru un CRM, module de contabilitate, inventar, achiziții, CMS și management de proiect, pe care le-aș fi folosit în curând.

Că totul s-a oprit, din punct de vedere al dezvoltării și al performanței, din cauza... ați ghicit, optimizării.

Am văzut că obiectele (reprezentate ca rânduri de tabel) ar putea avea multe relații diferite între ele în lumea reală și că am putea beneficia de urmărirea acestor relații: vom păstra mai multe informații și, în cele din urmă, am putea automatiza analiza de afaceri peste tot. Văzând asta ca o problemă de inginerie, am făcut ceva care părea ca o optimizare a flexibilității sistemului.

În acest moment, este important să ai grijă de fața ta, pentru că nu voi fi tras la răspundere dacă palma te doare. Gata? Am creat două tabele: relationship și unul la care a avut o referință la cheie externă, relationship_type . relationship se poate referi la oricare două rânduri oriunde în întreaga bază de date și poate descrie natura relației dintre ele.

Tabele baze de date: angajat, companie, relație, tip_relație

Oh omule. Tocmai optimizasem acea flexibilitate atât de mult .

Prea mult, de fapt. Acum am avut o nouă problemă: un anumit relationship_type nu ar avea sens între fiecare combinație dată de rânduri. Deși ar putea avea sens ca o person să aibă o relație employed by o company , aceasta nu ar putea fi niciodată echivalentă din punct de vedere semantic cu relația dintre, să zicem, două document .

OK nici o problema. Vom adăuga doar două coloane la relationship_type , specificând la ce tabele ar putea fi aplicată această relație. (Aici punctele bonus dacă ghiciți că m-am gândit să normalizez acest lucru prin mutarea celor două coloane într-un nou tabel care se referă la relationship_type.id , astfel încât relațiile care s- ar putea aplica semantic la mai mult de o pereche de tabele să nu aibă numele tabelelor duplicate. La urma urmei, dacă trebuia să schimb numele unui tabel și uitasem să-l actualizez în toate rândurile aplicabile, ar putea crea o eroare! Privind retrospectiv, cel puțin bug-urile ar fi oferit hrană păianjenilor care locuiesc în craniul meu.)

Tabelele bazei de date: relationship_type și applicable_to și datele complicate ale celor două coloane ale relationship_type reprezentate prin săgeți

Din fericire, am rămas inconștientă într-o furtună cu indicii înainte de a călători prea departe pe această cale. Când m-am trezit, mi-am dat seama că am reușit, mai mult sau mai puțin, să reimplementez tabelele interne legate de cheile externe ale RDBMS pe deasupra. În mod normal, mă bucur de momentele care se termină cu declarația uimitoare că „Sunt atât de meta”, dar acesta, din păcate, nu a fost unul dintre ele. Uitați să nu reușesc să scalați — umflarea îngrozitoare a acestui design a făcut ca back-end-ul aplicației mele încă simplă, a cărei DB a fost încă aproape inutilizabil cu date de testare.

Folosește cheile străine, Luke!

Să revenim pentru o secundă și să aruncăm o privire la două dintre numeroasele valori aflate în joc aici. Una este flexibilitatea, care a fost scopul meu declarat. În acest caz, optimizarea mea, fiind de natură arhitecturală, nici măcar nu a fost prematură:

Pași de optimizare a codului: Arhitectura este prima parte a unui program de optimizat

(Vom ajunge la asta în articolul meu publicat recent, Cum să evitați blestemul optimizării premature.) Cu toate acestea, soluția mea a eșuat spectaculos, fiind mult prea flexibilă. Cealaltă măsurătoare, scalabilitatea, a fost una pe care nici măcar nu mă gândeam încă, dar am reușit să o distrug cel puțin la fel de spectaculos cu daune colaterale.

Așa este, „Oh”.

Facepalm dublu, pentru când un singur facepalm nu îl taie

Aceasta a fost o lecție puternică pentru mine despre cum optimizarea poate merge complet prost. Perfecționismul meu a explodat complet: inteligența mea m-a determinat să produc una dintre cele mai obiective soluții neinteligente pe care le-am făcut vreodată.

Optimizați-vă obiceiurile, nu codul

Pe măsură ce vă surprindeți că aveți tendința de a refactoriza înainte de a avea măcar un prototip funcțional și o suită de teste pentru a dovedi corectitudinea acestuia, luați în considerare unde altundeva puteți canaliza acest impuls. Sudoku și Mensa sunt grozave, dar poate că ceva care va beneficia direct de proiect ar fi mai bine:

  1. Securitate
  2. Stabilitate de rulare
  3. Claritate și stil
  4. Eficiența codării
  5. Testează eficacitatea
  6. Profilare
  7. Setul dvs. de instrumente/DE
  8. USCAT (nu te repeta)

Dar atenție: optimizarea oricăruia dintre acestea va fi în detrimentul altora. Cel puțin, vine cu prețul timpului.

Aici este ușor să vezi cât de multă artă există în crearea codului. Pentru oricare dintre cele de mai sus, vă pot spune povești despre cât de mult sau prea puțin s-a considerat a fi o alegere greșită. Cine se gândește aici este, de asemenea, o parte importantă a contextului.

De exemplu, în ceea ce privește DRY: La o slujbă pe care am avut-o, am moștenit o bază de cod care avea cel puțin 80% declarații redundante, deoarece autorul ei aparent nu știa cum și când să scrie o funcție. Celelalte 20% din cod erau în mod confuz de auto-similar.

Am fost însărcinat să îi adaug câteva funcții. O astfel de caracteristică ar trebui să fie repetată în tot codul care urmează să fie implementat și orice cod viitor ar trebui copiat cu atenție pentru a utiliza noua caracteristică.

Evident, trebuia refactorizat doar pentru propria mea minte (valoare mare) și pentru viitorii dezvoltatori. Dar, pentru că eram nou la baza de cod, am scris mai întâi teste, astfel încât să mă pot asigura că refactorizarea mea nu a introdus nicio regresie. De fapt, au făcut exact asta: am prins două erori pe parcurs pe care nu le-aș fi observat printre toate rezultatele gobbledygook pe care le-a produs scriptul.

Până la urmă, am crezut că m-am descurcat destul de bine. După refactorizare, mi-am impresionat șeful că am implementat ceea ce fusese considerat o caracteristică dificilă cu câteva linii simple de cod; în plus, codul a fost în general cu un ordin de mărime mai performant. Dar nu a trecut prea mult timp după asta când același șef mi-a spus că am fost prea lent și că proiectul ar fi trebuit să se fi terminat deja. Traducere: Eficiența codării a fost o prioritate mai mare.

Atenție: optimizarea oricărui [aspect] anume va fi în detrimentul altora. Cel puțin, vine cu prețul timpului.

Încă cred că am urmat cursul corect acolo, chiar dacă optimizarea codului nu a fost apreciată direct de șeful meu de atunci. Fără refactorizare și teste, cred că ar fi durat mai mult pentru a fi corectă, adică, concentrarea asupra vitezei de codificare ar fi dejucat. (Hei, asta e tema noastră!)

Comparați acest lucru cu unele lucrări pe care am făcut-o într-un mic proiect secundar de-al meu. În proiect, încercam un nou motor de șabloane și am vrut să intru în obiceiuri bune de la început, deși încercarea noului motor de șabloane nu a fost scopul final al proiectului.

De îndată ce am observat că câteva blocuri pe care le adăugasem erau foarte asemănătoare între ele și, în plus, fiecare bloc trebuia să se facă referire la aceeași variabilă de trei ori, mi-a sunat clopoțelul USCAT în cap și am pornit să găsesc cea potrivită. mod de a face ceea ce încercam să fac cu acest motor de șablon.

S-a dovedit, după câteva ore de depanare inutilă, că acest lucru nu era posibil în prezent cu motorul de șablon în modul în care mi-am imaginat-o. Nu numai că nu a existat o soluție DRY perfectă ; nu a existat deloc soluție USCATĂ!

Încercând să optimizez această valoare a mea, mi-am deraiat complet eficiența de codare și fericirea, deoarece acest ocol mi-a costat proiectul progresul pe care l-aș fi putut avea în ziua aceea.

Chiar și atunci, m-am înșelat complet? Uneori, merită puțină investiție, în special într-un context tehnologic nou, pentru a cunoaște cele mai bune practici mai devreme și nu mai târziu. Mai puțin cod de rescris și obiceiuri proaste de anulat, nu?

Nu, cred că a fost neînțelept chiar și să caut o modalitate de a reduce repetiția în codul meu – în contrast puternic cu atitudinea mea din anecdota anterioară. Motivul este că contextul este totul: exploram o nouă piesă de tehnologie într-un mic proiect de joacă, fără să mă stabilesc pe termen lung. Câteva rânduri în plus și repetarea nu ar fi rănit pe nimeni, dar pierderea concentrării mi-a rănit pe mine și pe proiectul meu.

Stai, deci căutarea celor mai bune practici poate fi un obicei prost? Uneori. Dacă scopul meu principal ar fi învățarea noului motor sau învățarea în general, atunci acesta ar fi fost timp petrecut bine: mânuirea, găsirea limitelor, descoperirea caracteristicilor care nu au legătură și probleme prin cercetare. Dar uitasem că acesta nu era scopul meu principal și m-a costat.

Este o artă, așa cum am spus. Iar dezvoltarea acestei arte beneficiază de reamintirea „ Nu o face” . Cel puțin te face să iei în considerare ce valori sunt în joc în timp ce lucrezi și care sunt cele mai importante pentru tine în contextul tău .

Dar a doua regulă? Când putem optimiza efectiv?

2. Nu o faceți încă : cineva a făcut deja asta

Bine, fie de dvs., fie de altcineva, descoperiți că arhitectura dvs. a fost deja setată, fluxurile de date au fost gândite și documentate și este timpul să codificați.

Să ducem Nu o face încă un pas și mai departe: nici măcar nu-l codificați încă .

Acest lucru în sine poate mirosi a optimizare prematură, dar este o excepție importantă. De ce? Pentru a evita temutul NIHS, sau sindromul „Nu a fost inventat aici”, presupunând că prioritățile dvs. includ performanța codului și reducerea timpului de dezvoltare. Dacă nu, dacă obiectivele dvs. sunt complet orientate spre învățare, puteți sări peste această secțiune următoare.

Deși este posibil ca oamenii să reinventeze roata pătrată din pură orgoliu, cred că oamenii cinstiți și umili, ca tine și mine, pot face această greșeală doar neștiind toate opțiunile disponibile. Cunoașterea fiecărei opțiuni pentru fiecare API și instrument din stiva dvs. și păstrarea acestora pe măsură ce cresc și evoluează este cu siguranță multă muncă.

Dar, alocarea acestui timp este ceea ce te face un expert și te împiedică să fii a milionelea persoană de pe CodeSOD care să fie blestemată și batjocorită pentru urma de devastare lăsată în urmă de interpretarea lor fascinantă a calculatoarelor date-ora sau a manipulatoarelor de șiruri.

(Un bun contrapunct la acest model general este vechiul Java Calendar API, dar de atunci a fost remediat.)

Verificați biblioteca standard, verificați ecosistemul cadrului dvs., verificați dacă există FOSS care vă rezolvă deja problema

Sunt șanse ca conceptele cu care aveți de-a face să aibă nume destul de standard și binecunoscute, așa că o căutare rapidă pe internet vă va economisi o mulțime de timp.

De exemplu, recent mă pregăteam să fac o analiză a strategiilor AI pentru un joc de masă. M-am trezit într-o dimineață realizând că analiza pe care o plănuiam ar putea fi făcută cu ordine de mărime mai eficient dacă aș folosi pur și simplu un anumit concept de combinatorie de care mi-am amintit. Nefiind interesat să descopăr singur algoritmul pentru acest concept în acest moment, eram deja înainte știind numele potrivit de căutat. Cu toate acestea, am constatat că, după aproximativ 50 de minute de cercetare și de a încerca un cod preliminar, nu am reușit să transform pseudo-codul pe jumătate terminat pe care îl găsisem într-o implementare corectă. (Poți să crezi că există o postare pe blog în care autorul presupune o ieșire incorectă a algoritmului, implementează algoritmul incorect pentru a se potrivi cu ipotezele, comentatorii subliniază acest lucru, iar apoi ani mai târziu, încă nu este rezolvată?) În acel moment, ceaiul meu de dimineață am început și am căutat [name of concept] [my programming language] . 30 de secunde mai târziu, aveam codul corect din GitHub și treceam la ceea ce îmi doream de fapt să fac. Doar să devin specific și să includă limbajul, în loc să presupun că va trebui să-l implementez eu, a însemnat totul.

Este timpul să vă proiectați structura datelor și să vă implementați algoritmul

...din nou, nu juca coduri de golf. Prioritizează corectitudinea și claritatea în proiectele din lumea reală.

Investiție de timp: 10 ore, Timp de execuție: +25%, Utilizare memorie: +3%, Confuzie: 100%

Bine, așa că te-ai uitat și nu există nimic care să rezolve problema deja încorporat în lanțul tău de instrumente sau cu licență liberală pe web. Îți lansezi propriul tău.

Nici o problemă. Sfatul este simplu, în această ordine:

  1. Proiectați-l astfel încât să fie simplu de explicat unui programator începător.
  2. Scrieți un test care se potrivește așteptărilor produse de acel design.
  3. Scrieți codul astfel încât un programator începător să poată culege cu ușurință designul din acesta.

Simplu, dar poate greu de urmărit. Aici intră în joc obiceiurile de codificare și mirosurile de cod și arta, meșteșugurile și eleganța. În mod evident, există un aspect de inginerie în ceea ce faci în acest moment, dar din nou, nu juca cod golf. Prioritizează corectitudinea și claritatea în proiectele din lumea reală.

Dacă vă plac videoclipurile, iată unul dintre cineva care urmează pașii de mai sus, mai mult sau mai puțin. Pentru cei defavorizați de video, voi rezuma: este un test de codare a algoritmului la un interviu de angajare Google. Persoana intervievată proiectează mai întâi algoritmul într-un mod care este ușor de comunicat. Înainte de a scrie orice cod, există exemple de rezultat așteptat de un design funcțional. Apoi codul urmează în mod natural.

În ceea ce privește testele în sine, știu că în unele cercuri, dezvoltarea bazată pe teste poate fi controversată. Cred că o parte din motiv este că poate fi exagerat, urmărit religios până la punctul de a sacrifica timpul de dezvoltare. (Din nou, ne împușcăm în picior încercând să optimizăm prea mult chiar și o variabilă de la început.) Nici Kent Beck nu duce TDD la o asemenea extremă și a inventat programarea extremă și a scris cartea despre TDD. Deci, începeți cu ceva simplu pentru a vă asigura că rezultatul este corect. La urma urmei, oricum ai face asta manual după codare, nu? (Îmi cer scuze dacă sunteți un programator atât de rockstar încât nici măcar nu rulați codul după ce l-ați scris prima dată. În acest caz, poate ați lua în considerare să lăsați viitorii menținători ai codului dvs. cu un test doar ca să știți că nu vor întrerupeți-vă implementarea minunată.) Deci, în loc să faceți o diferență manuală, vizuală, cu un test, deja lăsați computerul să facă lucrul acesta pentru dvs.

În timpul procesului destul de mecanic de implementare a algoritmilor și structurilor de date, evitați să faceți optimizări linie cu linie și nici măcar nu vă gândiți să utilizați un limbaj extern personalizat de nivel inferior (Asamblare dacă codați în C, C dacă codificați în Perl etc.) în acest moment. Motivul este simplu: dacă algoritmul dvs. este înlocuit în întregime - și nu veți afla decât mai târziu în proces dacă este necesar - atunci eforturile dvs. de optimizare la nivel scăzut nu vor avea niciun efect în cele din urmă.

Un exemplu ECMAScript

Pe excelentul site de revizuire a codului comunitar exercism.io, am găsit recent un exercițiu care sugera în mod explicit să încercăm fie optimizarea pentru deduplicare, fie pentru claritate. Am optimizat pentru deduplicare, doar pentru a arăta cât de ridicole pot deveni lucrurile dacă iei DRY – o mentalitate de codificare altfel benefică, așa cum am menționat mai sus – prea departe. Iată cum arăta codul meu:

 const zeroPhrase = "No more"; const wallPhrase = " on the wall"; const standardizeNumber = number => { if (number === 0) { return zeroPhrase; } return '' + number; } const bottlePhrase = number => { const possibleS = (number === 1) ? '' : 's'; return standardizeNumber(number) + " bottle" + possibleS + " of beer"; } export default class Beer { static verse(number) { const nextNumber = (number === 0) ? 99 : (number - 1); const thisBottlePhrase = bottlePhrase(number); const nextBottlePhrase = bottlePhrase(nextNumber); let phrase = thisBottlePhrase + wallPhrase + ", " + thisBottlePhrase.toLowerCase() + ".\n"; if (number === 0) { phrase += "Go to the store and buy some more"; } else { const bottleReference = (number === 1) ? "it" : "one"; phrase += "Take " + bottleReference + " down and pass it around"; } return phrase + ", " + nextBottlePhrase.toLowerCase() + wallPhrase + ".\n"; } static sing(start = 99, end = 0) { return Array.from(Array(start - end + 1).keys()).map(offset => { return this.verse(start - offset); }).join('\n'); } }

Aproape nicio dublare de șiruri acolo! Scriindu-l în acest fel, am implementat manual o formă de compresie a textului pentru cântecul de bere (dar numai pentru cântecul de bere). Care a fost beneficiul, mai exact? Ei bine, să presupunem că vrei să cânți despre bea bere din cutii în loc de sticle. Aș putea realiza acest lucru schimbând o singură instanță de bottle în can .

Grozav!

…dreapta?

Nu, pentru că atunci toate testele se întrerup. OK, este ușor de remediat: vom căuta și înlocui bottle în specificațiile testului unitar. Și acest lucru este exact la fel de ușor de făcut ca și codul în sine și prezintă aceleași riscuri de a rupe lucrurile neintenționat.

Între timp, variabilele mele vor fi denumite ciudat după aceea, lucruri precum bottlePhrase neavând nicio legătură cu sticlele . Singura modalitate de a evita acest lucru este să fi prevăzut exact tipul de modificare care ar fi făcută și să fi folosit un termen mai generic, cum ar fi vessel sau container în locul bottle în numele variabilelor mele.

Înțelepciunea pregătirii pentru viitor în acest fel este destul de discutabilă. Care sunt șansele să vrei să schimbi ceva? Și dacă o faci, ceea ce schimbi va funcționa atât de convenabil? În exemplul bottlePhrase , ce se întâmplă dacă doriți să localizați într-o limbă care are mai mult de două forme de plural? Așa este, timpul de refactorizare, iar codul poate arăta și mai rău după aceea.

Dar când cerințele tale se schimbă și nu încerci doar să le anticipezi, atunci poate că este timpul să refactorezi. Sau poate mai poți amâna: câte tipuri de vase sau locații vei adăuga, în mod realist? Oricum, atunci când trebuie să echilibrați deduplicarea cu claritatea, merită să urmăriți această demonstrație a Katrinei Owen.

Revenind la propriul meu exemplu urât: Inutil să spun că beneficiile deduplicării nici măcar nu sunt realizate aici atât de mult. Între timp, cât a costat?

În afară de faptul că durează mai mult pentru a scrie, în primul rând, este acum destul de mai puțin trivial de citit, depanat și întreținut. Imaginați-vă nivelul de lizibilitate cu o cantitate moderată de duplicare permisă. De exemplu, având fiecare dintre cele patru variante de versuri scrise.

Dar încă nu ne-am optimizat!

Acum că algoritmul dvs. este implementat și ați dovedit că rezultatul său este corect, felicitări! Ai o linie de bază!

În sfârșit, este timpul să…optimizezi, nu? Nu, încă nu o faceți . Este timpul să vă luați baza și să faceți un benchmark frumos. Stabiliți un prag pentru așteptările dvs. în acest sens și inserați-l în suita dvs. de teste. Apoi, dacă ceva încetinește brusc acest cod - chiar dacă încă funcționează - veți ști înainte ca acesta să iasă pe ușă.

Așteptați în continuare optimizarea, până când aveți o parte întreagă din experiența relevantă a utilizatorului implementată. Până în acel moment, este posibil să vizați o parte complet diferită a codului decât este necesar.

Terminați aplicația (sau componenta), dacă nu ați făcut-o deja, setând toate liniile de referință algoritmice pe măsură ce mergeți.

Odată terminat acest lucru, acesta este un moment excelent pentru a crea și compara teste end-to-end care acoperă cele mai comune scenarii de utilizare din lumea reală a sistemului dumneavoastră.

Poate vei descoperi că totul este bine.

Sau poate ai stabilit că, în contextul său real, ceva este prea lent sau necesită prea multă memorie.

OK, acum poți optimiza

Există un singur mod de a fi obiectiv în privința asta. Este timpul să dezvăluiți graficele de flacără și alte instrumente de profilare. Inginerii cu experiență pot ghici sau nu mai bine mai des decât începătorii, dar nu acesta este ideea: singura modalitate de a ști sigur este să profilezi. Acesta este întotdeauna primul lucru de făcut în procesul de optimizare a codului pentru performanță.

Vă puteți profila în timpul unui anumit test end-to-end pentru a obține ceea ce va avea cu adevărat cel mai mare impact. (Și mai târziu, după implementare, monitorizarea tiparelor de utilizare este o modalitate excelentă de a rămâne la curent cu aspectele sistemului dvs. care sunt cele mai relevante pentru a fi măsurate în viitor.)

Rețineți că nu încercați să utilizați profiler-ul până la toată profunzimea sa - căutați mai mult profilarea la nivel de funcție decât profilarea la nivel de declarație, în general, deoarece scopul dvs. în acest moment este doar să aflați care algoritm este blocajul. .

Acum că ați folosit profilarea pentru a identifica blocajul sistemului dvs., acum puteți încerca efectiv să optimizați, încrezător că optimizarea dvs. merită făcută. De asemenea, puteți dovedi cât de eficientă (sau ineficientă) a fost încercarea dvs., datorită acelor repere de referință pe care le-ați făcut pe parcurs.

Tehnici de ansamblu

În primul rând, nu uitați să rămâneți la nivel înalt cât mai mult posibil:

La nivelul întregului algoritm, o tehnică este reducerea puterii. În cazul reducerii buclelor la formule, totuși, aveți grijă să lăsați comentarii. Nu toată lumea știe sau își amintește fiecare formulă combinatorică. De asemenea, fii atent la utilizarea matematicii: uneori ceea ce crezi că ar putea fi o reducere a puterii nu este, în cele din urmă. De exemplu, să presupunem că x * (y + z) are o semnificație algoritmică clară. Dacă creierul tău a fost antrenat la un moment dat, din orice motiv, să degrupeze automat termeni similari, ai putea fi tentat să rescrii ca x * y + x * z . În primul rând, acest lucru pune o barieră între cititor și semnificația algoritmică clară care a fost acolo. (Mai rău încă, acum este de fapt mai puțin eficient din cauza operațiunii suplimentare de înmulțire necesare. Este ca și cum desfășurarea buclei și-a plimbat pantalonii.) În orice caz, o notă rapidă despre intențiile tale ar ajuta mult și chiar te-ar putea ajuta să-ți vezi propria eroare înainte de a o comite.

Indiferent dacă utilizați o formulă sau doar înlocuiți un algoritm bazat pe buclă cu un alt algoritm bazat pe buclă, sunteți gata să măsurați diferența.

Dar poate că puteți obține performanțe mai bune pur și simplu schimbând structura datelor. Educați-vă asupra diferențelor de performanță dintre diferitele operațiuni pe care trebuie să le faceți asupra structurii pe care o utilizați și asupra oricăror alternative. Poate că un hash pare puțin mai dezordonat să funcționeze în contextul dvs., dar merită timpul de căutare superior pentru o matrice? Acestea sunt tipurile de compromisuri despre care depinde de dvs. să decideți.

Este posibil să observați că acest lucru se rezumă la a ști ce algoritmi sunt executați în numele dvs. atunci când apelați o funcție de confort. Deci este într-adevăr același lucru cu reducerea forței, în cele din urmă. Și să știi ce fac bibliotecile furnizorului tău în culise este crucial nu doar pentru performanță, ci și pentru evitarea erorilor neintenționate.

Micro-optimizări

OK, funcționalitatea sistemului tău este finalizată, dar din punct de vedere UX, performanța ar putea fi reglată puțin mai mult. Presupunând că ați făcut tot ce puteți mai sus, este timpul să luăm în considerare optimizările pe care le-am evitat tot timpul până acum. Luați în considerare, deoarece acest nivel de optimizare este încă un compromis față de claritate și întreținere. Dar ați decis că este timpul, așa că continuați cu profilarea la nivel de declarație, acum că vă aflați în contextul întregului sistem, unde de fapt contează.

La fel ca în cazul bibliotecilor pe care le utilizați, au fost puse nenumărate ore de inginerie în beneficiul dumneavoastră la nivelul compilatorului sau interpretului dumneavoastră. (La urma urmei, optimizarea compilatorului și generarea de cod sunt subiecte uriașe ale lor). Acest lucru este valabil chiar și la nivel de procesor. Încercarea de a optimiza codul fără a fi conștient de ceea ce se întâmplă la cele mai joase niveluri este ca și cum ai crede că a avea tracțiune integrală înseamnă că și vehiculul tău se poate opri mai ușor.

Este greu să oferi un sfat generic bun dincolo de asta, deoarece depinde într-adevăr de tehnologia ta și de ceea ce indică profilul tău. Dar, pentru că măsori, ești deja într-o poziție excelentă de a cere ajutor, dacă soluțiile nu ți se prezintă organic și intuitiv din contextul problemei. (De asemenea, somnul și timpul petrecut gândindu-se la altceva pot ajuta.)

În acest moment, în funcție de context și de cerințele de scalare, Jeff Atwood ar sugera probabil să adăugați pur și simplu hardware, care poate fi mai ieftin decât timpul dezvoltator.

Poate nu mergi pe acel drum. În acest caz, poate ajuta să explorați diferite categorii de tehnici de optimizare a codului:

  • Memorarea în cache
  • Bit hack-uri și cele specifice mediilor pe 64 de biți
  • Optimizarea buclei
  • Optimizarea ierarhiei memoriei

Mai exact:

  • Sfaturi de optimizare a codului în C și C++
  • Sfaturi de optimizare a codului în Java
  • Optimizarea utilizării procesorului în .NET
  • Memorarea în cache a fermei web ASP.NET
  • Reglarea bazei de date SQL sau reglarea Microsoft SQL Server în special
  • Scaling Scala's Play! cadru
  • Optimizare avansată a performanței WordPress
  • Optimizarea codului cu prototipuri JavaScript și lanțuri de scop
  • Optimizarea performanței React
  • Eficiența animației iOS
  • Sfaturi de performanță Android

În orice caz, mai am câteva lucruri care nu trebuie să faci pentru tine:

Nu reutilizați o variabilă în mai multe scopuri distincte. În ceea ce privește mentenabilitatea, este ca și cum ați rula o mașină fără ulei. Numai în cele mai extreme situații încorporate acest lucru a avut vreodată sens și chiar și în acele cazuri, aș susține că nu mai are vreodată. Aceasta este sarcina compilatorului de a organiza. Fă-o singur, apoi mută o linie de cod și ai introdus o eroare. Merită asta pentru tine iluzia salvării memoriei?

Nu utilizați macrocomenzi și funcții inline fără să știți de ce. Da, costul general al apelului de funcție este un cost. Dar evitarea acestuia face adesea ca codul să fie mai greu de depanat și, uneori, de fapt, îl face mai lent. Folosirea acestei tehnici peste tot pentru că este o idee bună din când în când este un exemplu de ciocan de aur.

Nu derulați manual bucle. Din nou, această formă de optimizare a buclei este ceva aproape întotdeauna mai bine optimizat printr-un proces automatizat cum ar fi compilarea, nu prin sacrificarea lizibilității codului.

Ironia din ultimele două exemple de optimizare a codului este că pot fi de fapt anti-performante. Desigur, din moment ce faci benchmark-uri, poți dovedi sau infirma asta pentru codul tău specific. Dar chiar dacă observați o îmbunătățire a performanței, reveniți la partea artistică și vedeți dacă câștigul merită pierderea în lizibilitate și întreținere.

Este al tău: Optimizare optimizată optim

Încercarea de optimizare a performanței poate fi benefică. De cele mai multe ori, totuși, se face foarte prematur, poartă cu sine o serie de efecte secundare rele și, cel mai ironic, duce la o performanță mai proastă. Sper că ați ieșit cu o apreciere extinsă pentru arta și știința optimizării și, cel mai important, contextul corespunzător.

Mă bucur dacă acest lucru ne ajută să renunțăm la noțiunea de a scrie codul perfect de la început și să scriem în schimb codul corect. Trebuie să ne amintim să optimizăm de sus în jos, să dovedim unde se află blocajele și să măsurăm înainte și după remedierea lor. Aceasta este strategia optimă, optimă pentru optimizarea optimizării. Mult noroc.