Optimizarea performanței este una dintre cele mai mari amenințări la adresa codului dvs.
Poate că te gândești, nu alta acestea oameni . Înțeleg. Optimizarea de orice fel ar trebui să fie în mod clar un lucru bun, judecând după etimologia sa, așa că, în mod firesc, vrei să fii bun la asta.
Nu doar pentru a te distinge de mulțime ca un dezvoltator mai bun. Nu doar pentru a evita să fii „Dan” pe Daily WTF , dar pentru că credeți că optimizarea codului este ceea ce trebuie făcut. Te mândrești cu munca ta.
Hardware-ul computerului devine din ce în ce mai rapid și software mai ușor de făcut, dar orice lucru simplu pe care îl aveți Vreau doar să fii capabil să faci, la naiba durează întotdeauna mai mult decât precedentul. Dai din cap la acest fenomen (întâmplător, cunoscut sub numele de Legea lui Wirth) și te hotărăști să respingi această tendință.
E nobil de tine, dar oprește-te.
Sunteți în pericolul cel mai mare de a vă contracara propriile obiective, indiferent de cât de experimentat aveți în programare.
Cum așa? Să facem o copie de rezervă.
În primul rând, ce este optimizarea codului?
Adesea, când îl definim, presupunem că dorim codul a executa mai bine. Spunem că optimizarea codului este scrierea sau rescrierea codului, astfel încât un program utilizează cea mai mică memorie posibilă sau spațiu pe disc, își minimizează timpul procesorului sau lățimea de bandă a rețelei sau utilizează cel mai bine nucleele suplimentare.
În practică, uneori implicim o altă definiție: Scrierea mai puțin a codului.
Dar codul preventiv pe care îl scrieți cu acest scop este și mai probabil să devină un spin în partea cuiva. A caror? Următoarea persoană ghinionistă care trebuie să înțeleagă codul tău, care poate fi chiar tu. Și cineva inteligent și capabil, ca tine, poate evita autosabotajul: păstrează-ți scopurile nobile, dar reevaluează-ți mijloacele, în ciuda faptului că acestea par a fi incontestabil intuitive.
Deci, optimizarea codului este un termen cam vag. Asta chiar înainte de a lua în considerare unele dintre celelalte modalități prin care se poate optimiza codul, pe care le vom descrie mai jos.
Să începem prin a asculta sfaturile înțelepților în timp ce explorăm împreună Jackson Celebrele reguli de optimizare a codului:
Voi începe cu un exemplu destul de jenant de extrem, dintr-un timp în care, cu mult timp în urmă, tocmai îmi umezeam picioarele în minunata lume a SQL-ului, care are „tortul tău și mănâncă-l”. Problema a fost că am călcat pe tort și nu am mai vrut să-l mănânc, deoarece era ud și a început să miroasă a picioare.
Tocmai îmi umezeam picioarele în minunata lume a SQL-ului. Problema a fost că am călcat pe tort ...
Aștepta. Lasă-mă să mă întorc din epava mașinii unei metafore pe care tocmai am făcut-o și explic.
Faceam cercetare și dezvoltare pentru o aplicație intranet, care speram că va deveni într-o zi un sistem de management complet integrat pentru mica afacere unde am lucrat. Ar urmări 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 un fișier plin de casă pe care alți dezvoltatori îl folosiseră. Am vrut să proiectez totul cât mai inteligent posibil de la început pentru că aveam o ardezie goală. Ideile pentru acest sistem explodau ca niște 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 în curând le-aș fi dovedit.
Că totul s-a oprit, dezvoltare- și din punct de vedere al performanței, din cauza ... ați ghicit, optimizare.
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: am păstra mai multe informații și am putea automatiza în cele din urmă analiza afacerii peste tot. Văzând acest lucru ca pe o problemă de inginerie, am făcut ceva care părea o optimizare a flexibilității sistemului.
În acest moment, este important să te uiți la fața ta, pentru că nu voi fi tras la răspundere dacă te doare palma. Gata? Am creat două tabele: relationship
și unul la care se referea o cheie străină, relationship_type
. relationship
ar putea face referire la oricare două rânduri oriunde în întreaga bază de date și să descrie natura relației dintre ele.
Oh omule. Tocmai optimizasem acea flexibilitate atât de al naibii de mult .
Prea mult, de fapt. Acum am avut o nouă problemă: o dată relationship_type
în mod natural nu ar avea sens între fiecare combinație dată de rânduri. Deși ar putea avea sens că un person
a avut un employed by
relație cu o company
, care nu ar putea fi niciodată echivalentă semantic cu relația dintre, să zicem, două document
s.
OK nici o problema. Vom adăuga doar două coloane la relationship_type
, specificând la ce tabele s-ar putea aplica această relație. (Puncte bonus aici dacă ghiciți că m-am gândit să normalizez acest lucru mutând cele două coloane într-un nou tabel referitor la relationship_type.id
, astfel încât relațiile care ar putea aplicarea semantic la mai multe perechi de tabele nu ar avea numele tabelelor duplicate. La urma urmei, dacă ar fi nevoie să schimb un nume de tabel și am uitat să îl actualizez în toate rândurile aplicabile, ar putea crea o eroare! Retrospectiv, cel puțin bug-urile ar fi furnizat hrană păianjenilor care locuiesc în craniul meu.)
Din fericire, am fost bătut 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 cheie externă ale RDBMS deasupra sa. În mod normal, mă bucur de momente care se termină cu faptul că fac proclamația înfricoșătoare că „Sunt atât de meta”, dar aceasta, din păcate, nu a fost una dintre ele. A uita nereușind să scară - balonarea oribilă a acestui design a făcut ca back-end-ul aplicației mele încă simple, al cărui DB să nu fie încă populat cu date de testare, să fie aproape inutilizabil.
Să facem o copie de rezervă pentru o secundă și să aruncăm o privire la două dintre multele valori în joc aici. Una este flexibilitatea, care fusese scopul meu declarat. În acest caz, optimizarea mea, fiind de natură arhitecturală, nu a fost nici măcar prematură:
(Vom ajunge mai departe la articolul publicat recent, Cum să evitați blestemul optimizării premature .) Cu toate acestea, soluția mea a eșuat spectaculos, fiind departe de asemenea flexibil. Cealaltă valoare, scalabilitatea, era una pe care nici măcar nu o aveam în vedere, dar reușeam să o distrug cel puțin la fel de spectaculos cu pagube colaterale.
Așa este, „Oh”.
Aceasta a fost o lecție puternică pentru mine despre modul în care optimizarea poate merge complet în neregulă. Perfecționismul meu a implodat complet: istețimea mea mă condusese să produc una dintre cele mai obiectiv soluții neinteligente pe care le-am făcut vreodată.
Pe măsură ce te surprinzi, având tendința de refactorizare, înainte de a avea chiar și un prototip funcțional și o suită de teste pentru a demonstra corectitudinea acestuia, ia în considerare unde altundeva poți canaliza acest impuls. Sudoku și Mensa sunt minunate, dar poate că ceva care va beneficia în mod direct proiectul dvs. ar fi mai bun:
Dar atenție: optimizarea heck-ului pentru oricare dintre acestea va costa cu alții. Cel puțin, vine cu prețul timpului.
Aici este ușor să vedeți cât de multă artă există în elaborarea codului. Pentru oricare dintre cele de mai sus, vă pot spune povești despre cât de mult sau prea puțin din el sa considerat că este alegerea greșită. Cine gândește aici este, de asemenea, o parte importantă a contextului.
De exemplu, în ceea ce privește DRY: La un loc de muncă pe care l-am avut, am moștenit o bază de cod care conținea declarații redundante de cel puțin 80%, deoarece autorul său aparent nu știa cum și când să scrie o funcție. Celelalte 20% din cod erau asemănătoare confuz.
Am fost însărcinat să îi adaug câteva funcții. O astfel de caracteristică ar trebui repetată de-a lungul întregului cod care urmează a fi implementat și orice cod viitor ar trebui să fie atent copypasta’d pentru a utiliza noua funcție.
Evident, trebuia refactorizat doar pentru propria mea sănătate (valoare ridicată) și pentru orice viitor dezvoltator. Dar, pentru că eram nou în baza de coduri, am scris mai întâi teste, astfel încât să mă pot asigura că refactorizarea mea nu introduce nicio regresie. De fapt, au făcut exact asta: am prins două erori pe parcurs pe care nu le-aș fi observat printre toate ieșirile gobbledygook pe care le-a produs scenariul.
În cele din urmă, am crezut că m-am descurcat destul de bine. După refactorizare, mi-am impresionat șeful prin implementarea a ceea ce fusese considerat o caracteristică dificilă cu câteva linii simple de cod; în plus, codul a fost în general un ordin de mărime mai performant. Dar nu a trecut prea mult timp după aceea, același șef mi-a spus că am fost prea lent și că proiectul ar fi trebuit deja să se termine. Traducere: Eficiența codării a fost o prioritate mai mare.
Feriți-vă: optimizarea heck pentru orice [aspect] anume va costa cu prețul altora. Cel puțin, vine cu prețul timpului.
Încă cred că am luat cursul potrivit acolo, chiar dacă optimizarea codului nu a fost apreciată direct de șeful meu la acea vreme. Fără refactorizare și teste, cred că ar fi trebuit mai mult timp pentru a deveni corect - de exemplu, concentrarea pe viteza de codare ar fi zădărnicit-o. (Hei, asta e tema noastră!)
Contrastează acest lucru cu unele lucrări pe care le-am făcut într-un mic proiect lateral al meu. În proiect, încercam un nou motor de șabloane și doream să ajung la obiceiuri bune de la început, chiar dacă încercarea noului motor de șabloane nu era 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 necesită referirea la aceeași variabilă de trei ori, clopotul DRY mi-a ieșit în cap și am pornit să găsesc locul potrivit mod de a face ceea ce încercam să fac cu acest motor de șabloane.
S-a dovedit, după câteva ore de depanare infructuos, că acest lucru nu a fost în prezent posibil cu motorul șablonului așa cum mi-am imaginat. Nu numai că nu a existat perfect Soluție uscată; nu a existat orice Soluție uscată deloc!
Încercând să optimizez această valoare a mea, mi-am deraiat complet eficiența de codificare și fericirea, deoarece acest ocol a costat proiectul meu progresul pe care l-aș fi putut avea în acea zi.
Chiar și atunci, m-am înșelat pe deplin? Uneori merită o investiție, în special cu un context tehnologic nou, pentru a cunoaște cele mai bune practici mai devreme decât mai târziu. Mai puțin cod de rescris și obiceiuri proaste de anulat, nu?
Nu, cred că a fost neînțelept căutând chiar o modalitate de a reduce repetarea codului meu - în contrast puternic cu atitudinea mea din anecdota anterioară. Motivul este că contextul este totul: exploram o nouă piesă tehnologică într-un proiect de joacă mică, nu mă așezam pe termen lung. Câteva linii suplimentare și repetarea nu ar fi rănit pe nimeni, dar pierderea concentrării mi-a rănit pe mine și proiectul meu.
Așteptați, deci căutarea celor mai bune practici poate fi un obicei prost? Uneori. Daca a mea principal Scopul era învățarea noului motor, sau învățarea în general, atunci acesta ar fi fost un timp bine petrecut: Gândire, găsirea limitelor, descoperirea trăsăturilor fără legătură și obținerea prin cercetare. Dar uitasem că acesta nu era obiectivul meu principal și m-a costat.
Este o artă, așa cum am spus. Și dezvoltarea artei respective beneficiază de memento, Nu o faceți . Cel puțin te face să iei în considerare care sunt valorile în joc în timp ce lucrezi și care sunt cele mai importante tu în ta context.
Dar a doua regulă? Când putem de fapt să optimizăm?
OK, indiferent dacă de dvs. sau de altcineva, descoperiți că arhitectura dvs. a fost deja setată, fluxurile de date au fost gândite și documentate și este timpul să codați.
Hai sa luam Nu o faceți î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 „Neinventat aici” - presupunând că prioritățile dvs. includ performanța codului și minimizarea 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 reinventează roata pătrată din lipsă de umilință, cred că oamenii cinstiți și umili, ca tine și cu mine, putem face această greșeală numai necunoscând toate opțiunile disponibile. Cunoașterea fiecărei opțiuni a fiecărui API și instrument din stiva dvs. și menținerea lor pe măsură ce cresc și evoluează este cu siguranță multă muncă.
Dar, introducerea acestui timp este ceea ce te face expert și te împiedică să fii cea de-a zecelea persoană pe CodeSOD care să fie blestemată și batjocorită pentru urmele devastării lăsate în urmă de fascinantele lor abordări de calculatoare de dată-timp sau manipulatoare de șiruri.
(Un bun contrapunct la acest tipar general este vechiul Java Calendar
API, dar de atunci a fost remediat .)
Este posibil ca conceptele cu care aveți de-a face să aibă nume destul de standard și bine cunoscute, astfel încât o căutare rapidă pe internet vă va economisi o grămadă de timp.
De exemplu, recent mă pregăteam să fac o analiză a strategiilor de AI pentru un joc de societate. M-am trezit într-o dimineață, dându-mi seama că analiza pe care o planificam putea fi făcută ordine de mărime mai eficient dacă aș folosi pur și simplu un anumit concept combinatoric pe care mi-l aminteam. Nefiind interesat să descopăr eu algoritmul pentru acest concept în acest moment, eram deja în avans știind numele potrivit de căutat. Cu toate acestea, am constatat că după aproximativ 50 de minute de cercetare și încercarea unor coduri preliminare, nu reușisem să transform pseudo-codul pe jumătate terminat pe care îl găsisem într-o implementare corectă. (Îți vine să crezi că există o postare pe blog acolo unde autorul presupune o ieșire incorectă a algoritmului, implementează algoritmul incorect pentru a se potrivi cu ipotezele, comentatorii arată acest lucru și apoi ani mai târziu, tot nu este rezolvat?) În acel moment, ceaiul meu de dimineață am dat cu piciorul și am căutat [name of concept] [my programming language]
. 30 de secunde mai târziu, aveam codul corect corect de la GitHub și treceam la ceea ce îmi dorisem de fapt să fac. Doar să fiu specific și să incluz limbajul, în loc să presupun că ar trebui să îl implementez eu însumi, însemna totul.
... din nou, nu te juca codul de golf . Prioritizează corectitudinea și claritatea în proiectele din lumea reală.
OK, deci ați arătat și nu există deja nimic care să vă rezolve problema încorporat în lanțul de instrumente sau care are licență generală pe web. Îl lansezi pe al tău.
Nici o problemă. Sfatul este simplu, în această ordine:
Simplu, dar poate greu de urmat. Aici sunt obiceiurile de codificare și codul miroase si arta si meșteșug și eleganța intră în joc. Există, evident, un aspect ingineresc în ceea ce faceți în acest moment, dar, din nou, nu jucați codul de golf . Prioritizează corectitudinea și claritatea în proiectele din lumea reală.
Dacă îți plac videoclipurile, iată unul dintre cei care urmează pașii de mai sus , mai mult sau mai putin. Pentru video-averse, voi rezuma: este un test de codare a algoritmului la un interviu de angajare Google. Intervievatul proiectează mai întâi algoritmul într-un mod ușor de comunicat. Înainte de a scrie orice cod, există exemple de rezultate așteptate 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 motivul pentru care este că poate fi exagerată, urmărită religios până la sacrificarea timpului de dezvoltare. (Din nou, împușcându-ne în picior încercând să optimizăm chiar și o variabilă prea mult de la început.) Chiar și Kent Beck nu duce TDD la o asemenea extremă și a inventat o programare extremă și a scris cartea pe TDD. Deci, începeți cu ceva simplu pentru a vă asigura că rezultatul dvs. este corect. La urma urmei, ai face asta manual după codificare oricum, nu? (Îmi cer scuze dacă sunteți un astfel de programator rockstar încât nici măcar nu vă rulați codul după ce l-ați scris prima dată. În acest caz, poate vă gândiți să lăsați viitorii mentenanți ai codului cu un test doar pentru a ști că ei nu vă va rupe implementarea extraordinară.) Deci, în loc să faceți o diferență manuală, vizuală, cu un test în loc, lăsați deja computerul să facă acest lucru 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 faceți acest lucru gândi de a utiliza un limbaj extern personalizat de nivel inferior (Asamblare dacă codificaț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 până mai târziu în acest proces dacă este necesar - atunci eforturile dvs. de optimizare la nivel scăzut nu vor avea niciun efect în cele din urmă.
Pe excelentul site de recenzie a codului comunitar exercism.io , Am găsit recent un exercitiu care a sugerat în mod explicit să încercați fie optimizarea pentru duplicare, fie pentru claritate. Am optimizat pentru deduplicare, doar pentru a arăta cât de ridicole pot obține lucrurile dacă luați DRY - o mentalitate de codare 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() + '.
'; 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 + '.
'; } static sing(start = 99, end = 0) { return Array.from(Array(start - end + 1).keys()).map(offset => { return this.verse(start - offset); }).join('
'); } }
Aproape că nu există deloc duplicarea șirurilor! Scriind-o în acest fel, am implementat manual o formă de compresie a textului pentru melodia berii (dar numai pentru melodia berii). Care a fost beneficiul, mai exact? Ei bine, să presupunem că vrei să cânți despre consumul de bere din conserve în loc de sticle. Aș putea realiza acest lucru schimbându-mă o singură instanță din bottle
la can
.
Grozav!
…dreapta?
Nu, pentru că atunci toate testele se rup. OK, este ușor de rezolvat: vom face doar o căutare și o vom înlocui pentru bottle
în specificația testului de unitate. Și acest lucru este exact la fel de ușor de făcut ca a face acest lucru codului în sine și prezintă aceleași riscuri de a sparge lucrurile neintenționat.
Între timp, variabilele mele vor fi numite ciudat după aceea, cu lucruri precum bottlePhrase
neavând nimic de-a face sticle deloc. Singura modalitate de a evita acest lucru este să fi prevăzut exact tipul de schimbare care ar fi făcută și să se utilizeze un termen mai generic, cum ar fi vessel
sau container
în locul bottle
în numele mele variabile.
Înțelepciunea în ceea ce privește rezistența la viitor este destul de discutabilă. Care sunt șansele pe care doriți să le schimbați deloc? Și dacă o faci, ceea ce schimbi va funcționa atât de convenabil? În bottlePhrase
de exemplu, ce se întâmplă dacă doriți să localizați într-o limbă care are mai mult de două forme de plural ? Este corect, timpul de refactorizare, iar codul ar putea arăta și mai rău după aceea.
Dar când cerințele tale do schimbați și nu încercați doar să le anticipați, apoi poate este timpul să refactorizăm. Sau poate o mai poți amâna: Câte tipuri de nave sau localizări vei adăuga, în mod realist? Oricum, atunci când trebuie să vă echilibrați deduplicarea cu claritatea, merită vizionat această demonstrație de Katrina Owen .
Înapoi la propriul meu exemplu urât: Inutil să spun că beneficiile deduplicării nici măcar nu se realizează aici atât de mult. Între timp, ce a costat?
În afară de a lua mai mult timp pentru a scrie, în primul rând, este acum puțin mai puțin banal să citești, să depanezi și să întreții. Imaginați-vă nivelul de lizibilitate cu o cantitate moderată de dublare permisă. De exemplu, având fiecare dintre cele patru variante ale versurilor precizate .
Acum că algoritmul dvs. este implementat și ați dovedit că rezultatul său este corect, felicitări! Aveți o linie de bază!
În cele din urmă, este timpul să ... optimizăm, nu? Nu, încă Nu o faceți încă . Este timpul să vă luați linia de bază și să faceți un lucru frumos reper . Setați un prag pentru așteptările dvs. în legătură cu acest lucru și fixați-l în suita de testare. Apoi, dacă ceva face brusc acest cod mai lent - chiar dacă funcționează în continuare - veți ști înainte de a ieși pe ușă.
Așteptați totuși optimizarea, până când veți implementa o bucată întreagă din experiența relevantă a utilizatorului. Până în acel moment, este posibil să vizați o parte complet diferită a codului decât trebuie.
Terminați aplicația (sau componenta), dacă nu ați făcut-o deja, stabiliți toate criteriile de referință algoritmice în timp ce mergeți.
Odată ce ați făcut acest lucru, acesta este un moment excelent pentru a crea și a face referințe la testele end-to-end care acoperă cele mai comune scenarii de utilizare din lumea reală a sistemului dvs.
Poate veți descoperi că totul este în regulă.
Sau poate ați stabilit că, în contextul său real, ceva este prea lent sau necesită prea multă memorie.
Există o singură modalitate de a fi obiectiv în acest sens. Este timpul să izbucnim grafice cu flacără și alte instrumente de profilare. Inginerii cu experiență pot sau nu să ghicească mai bine mai des decât începătorii, dar nu asta este ideea: singurul mod de a ști cu siguranță este de a profila. Acesta este întotdeauna primul lucru de făcut în procesul de optimizare a codului pentru performanță.
Puteți profila în timpul unui anumit test end-to-end pentru a ajunge la ceea ce va avea cu adevărat cel mai mare impact. (Și mai târziu, după implementare, monitorizarea modelelor de utilizare este o modalitate excelentă de a rămâne la curent cu aspectele sistemului dvs. care sunt cele mai relevante de măsurat în viitor.)
Rețineți că nu încercați să utilizați profilerul până la profunzimea sa maximă - căutați mai mult profilarea la nivel de funcție decât profilarea la nivel de instrucțiuni, în general, deoarece obiectivul 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 de fapt să optimizați, cu încredere că merită să o faceți. Puteți dovedi, de asemenea, cât de eficientă (sau ineficientă) a fost încercarea dvs., datorită acelor criterii de referință pe care le-ați făcut pe parcurs.
Mai întâi, nu uitați să rămâneți la nivel înalt cât mai mult posibil:
Știați? Cel mai bun truc universal de optimizare se aplică în toate cazurile:
- Lars Doucet (@larsiusprime) 30 martie 2017
- Desenați mai puține lucruri
- Actualizați mai puține lucruri
La nivelul întregului algoritm, o tehnică este reducerea puterii . Totuși, în cazul reducerii buclelor la formule, nu uitați să lăsați comentarii. Nu toată lumea știe sau își amintește fiecare formulă combinatorică. De asemenea, aveți grijă la utilizarea matematicii: uneori ceea ce credeți că ar putea fi reducerea puterii nu este, în cele din urmă. De exemplu, să presupunem că x * (y + z)
are o semnificație algoritmică clară. Dacă creierul dvs. a fost antrenat la un moment dat, indiferent de motiv, pentru a anula gruparea în mod automat a unor termeni asemănători, ați putea fi tentați să rescrieți acest lucru ca x * y + x * z
. În primul rând, acest lucru pune o barieră între cititor și semnificația algoritmică clară care fusese acolo. (Mai rău încă, acum este de fapt Mai puțin eficient datorită operației de multiplicare suplimentare necesare. Este ca și cum derularea buclelor și-a aruncat pantalonii.) În orice caz, o notă rapidă despre intențiile dvs. ar merge mult și ar putea chiar să vă ajute să vă vedeți 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ă cu privire la diferența 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 pentru a funcționa în contextul dvs., dar merită timpul de căutare superior peste o matrice? Acestea sunt tipurile de compromisuri de care depindeți de dvs.
Este posibil să observați că acest lucru se reduce la cunoașterea algoritmilor care 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 puterii, în cele din urmă. Și cunoașterea a ceea ce fac bibliotecile furnizorului dvs. în culise este crucială nu doar pentru performanță, ci de asemenea, pentru evitarea erorilor neintenționate .
OK, funcționalitatea sistemului dvs. este realizată, dar din punct de vedere UX, performanța ar putea fi ajustată puțin mai departe. Presupunând că ați făcut tot ce puteți mai sus, este timpul considera 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 împotriva clarității și mentenanței. Dar ați decis că este timpul, așa că continuați cu profilarea la nivel de declarații, acum că vă aflați în contextul întregului sistem, în care contează de fapt.
La fel ca și în cazul bibliotecilor pe care le utilizați, au fost puse la dispoziție nenumărate ore de inginerie la nivelul compilatorului sau al interpretului dvs. (La urma urmei, optimizarea compilatorului și generarea de cod sunt subiecte uriașe proprii ). Acest lucru este chiar adevărat la nivel de procesor . Încercarea de a optimiza codul fără conștient de ceea ce se întâmplă la cele mai mici 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ă dai sfaturi generice bune dincolo de asta, deoarece depinde într-adevăr de stiva ta tehnologică și de ceea ce indică profilerul tău. Dar, pentru că măsurați, sunteți deja într-o poziție excelentă de a cere ajutor, dacă soluțiile nu vă sunt prezentate organic și intuitiv din contextul problemei. (Somnul și timpul petrecut gândindu-vă la altceva pot ajuta, de asemenea.)
În acest moment, în funcție de context și de cerințele de scalare, Jeff Atwood ar sugera probabil pur și simplu adăugând hardware , care poate fi mai ieftin decât timpul dezvoltatorului.
Poate că nu mergeți pe această cale. În acest caz, vă poate ajuta să explorați diferite categorii de optimizarea codului tehnici:
Mai exact:
În orice caz, eu do mai ai ceva Nu trebuie Pentru dumneavoastră:
Nu refolosiți o variabilă în mai multe scopuri distincte. În termeni de întreținere, este ca și cum ai conduce o mașină fără ulei. Numai în cele mai extreme situații încorporate acest lucru a avut vreodată sens și, chiar și în aceste cazuri, aș argumenta că nu mai are. Aceasta este sarcina compilatorului de a organiza. Faceți-o singur, apoi mutați o linie de cod și ați introdus o eroare. Iluzia salvării memoriei merită asta?
Nu utilizați macrocomenzi și funcții inline fără să știți de ce. Da, funcția de cheltuieli generale este un cost. Dar evitarea acestuia face ca codul dvs. să fie mai greu de depanat și, uneori, îl face mai lent. Folosirea acestei tehnici peste tot doar pentru că este o idee bună din când în când este un exemplu de a ciocan de aur .
Nu derulați manual buclele. Din nou, această formă de optimizarea buclei este ceva aproape întotdeauna mai bun optimizat de un proces automat cum ar fi compilarea , nu sacrificând lizibilitatea codului dvs.
Ironia din ultimele două exemple de optimizare a codului este că acestea pot fi de fapt anti-performante. Desigur, deoarece faceți criterii de referință, puteți dovedi sau respinge acest lucru pentru codul dvs. particular. Dar chiar dacă vedeți o îmbunătățire a performanței, reveniți la partea de artă și vedeți dacă câștigul merită pierderea în lizibilitate și mentenabilitate.
Încercarea de optimizare a performanței poate fi benefică. De cele mai multe ori, totuși, se face foarte prematur, poartă cu sine o litanie de efecte secundare rele și, cel mai ironic, duce la performanțe mai slabe. Sper că ați venit cu o apreciere extinsă pentru arta și știința optimizării și, cel mai important, contextul adecvat al acesteia.
Mă bucur dacă acest lucru ne ajută să renunțăm la noțiunea de a scrie cod perfect de la început și să scriem 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 acestora. Aceasta este strategia optimă și optimă pentru optimizarea optimizării. Mult noroc.
Optimizarea software-ului se poate referi la multe aspecte ale unui software, cum ar fi flexibilitatea, mentenabilitatea sau performanța. Cu toate acestea, performanța este de obicei implicată.
Chiar și optimizarea performanței se poate referi la multe aspecte diferite ale codului. Uneori, aspectele pot fi în mod inerent în contradicție unele cu altele, cum ar fi dimensiunea pe disc față de timpul procesorului. Minimizarea timpului de rulare este cel mai comun obiectiv.
Optimizarea buclei transformă o buclă într-un mod care îmbunătățește performanța fără a modifica ieșirea. Deoarece multe transformări costă mentenabilitatea și lizibilitatea, de multe ori acest lucru este cel mai bine lăsat unui compilator.
Corectitudine, claritate, mentenabilitate, flexibilitate și, în mod ironic, chiar și aspectul specific optimizat (utilizarea memoriei, timpul de răspuns etc.). Mai multă ironie: optimizarea de la început nu economisește timp la final, când codul optimizat este înlocuit de o funcție încorporată sau de la o terță parte.
Optimizați mai întâi arhitectura și apoi (dacă le veți implementa chiar dvs.) algoritmi și structuri de date.
În primul rând, profilul. Chiar și experții identifică adesea în mod fals ceea ce trebuie optimizat. După aceea, pașii corespunzători sunt știința de bază: luați puncte de referință considerate cu atenție înainte și după efectuarea unei modificări. Fără context și măsurare corecte, optimizarea este o fotografie în întuneric ... într-o cameră plină de prieteni buni!