Trăim în timpuri exponențiale - după cum spunea unul dintre acele filmulețe populare pe YouTube și Facebook. Și poate nici unul dintre aspectele vieții noastre nu se conformează acestui dicton la fel de mult ca și cantitatea mereu crescândă de putere de procesare pe care o avem la îndemână. Legea lui Moore și mai ales cei care trudesc neostenit pentru a o menține în vigoare sunt în parte, de vină pentru acest lucru, dar cauza principală este fascinația noastră pentru mai mult, mai repede, mai mare - concepte pe care, în general, le asociem cu mai bine. Însă în această evoluție monoton crescătoare apar probleme atunci când ne lovim de un zid de netrecut, când trebuie să decidem dacă mai bine înseamnă mai repede sau înseamnă mai mult.
Acesta este punctul în care performanța procesării de date se află în acest moment. În general când se vorbește despre performanță se vorbește fie de latență, fie de volum (throughput), fie de ambele. Aceste metrici intră uneori în conflict, existând diverse tehnici pentru a optimiza una sau alta dintre ele. Latența este în general îmbunătățită prin creșterea performanței liniare sau cvasi-liniare (performanța single-threaded sau single-process) - spre deosebire de scalarea orizontală, care e în general asociată cu volum ridicat de procesare.
Așadar, cum am mai spus, întrebarea cheie este dacă mai repede poate fi atins prin mai mult - cu alte cuvinte, dacă performanța poate fi îmbunătățită folosind mai multe resurse, spre deosebire de a folosi o resursă mai rapidă (cum se făcea în mod tradițional). În acest articol, vom argumenta că, în ciuda tendinței generale actuale înspre scalare, nu există un răspuns general valabil și că răspunsul depinde de fiecare problemă în parte și că, în plus, există surprinzător de multe sectoare unde mai multe resurse nu ajută. Acest lucru ne pune în poziția de a fi nevoiți să combinăm performanța liniară cu scalarea orizontală pentru a rezolva problemele pe care le întâlnim.
Pentru multă vreme în istoria nu foarte lungă a calculatoarelor, tot ce trebuia să faci dacă voiai ca un program să ruleze mai repede era fie să îl rulezi pe un procesor mai rapid (ceea ce în general însemna frecvență de operare mai ridicată), fie să faci programul în sine mai rapid. Acest lucru nu mai este valabil de ceva vreme, ceea ce a dus la apariția unei noi paradigme bazată pe paralelizare și pe scalare. În următoarele paragrafe, vom trece pe scurt în revistă principalele momente care au dus la această transformare.
Fizica nu mai este la fel de prietenoasă cu proiectanții de calculatoare cum a fost cândva. Zidul ridicat de mama natură undeva în jurul pragului de 4 GHz s-a dovedit extrem de greu de escaladat în condițiile de mass market, făcându-i pe cei din industrie să caute noi modalități de a împinge mai departe limitele puterii de procesare. Mai multe alternative au fost explorate ( de exemplu, creșterea frecvenței de operare în ciuda costurilor, implicând rularea la temperaturi foarte scăzute sau cu consum mare de energie, ori trecerea la quantum computing ), însă soluția care s-a impus a fost reorientarea masivă spre paralelizare și spre scalarea paralelă (zisă și orizontală). Rezultatul: avem acum procesoare mainstream cu până la 20 de core-uri, și procesoare specializate pe diverse nișe cu mii de core-uri. Presupunerea era că tot acest paralelism disponibil la nivel de hardware va fi exploatat de trecerea la aplicații multithreaded - dar experiența a arătat că acest lucru s-a întâmplat într-o măsură redusă. Din diverse motive, în general ținând de arhitectura software-ului, majoritatea aplicațiilor încă se bazează pe câteva threaduri principale, ceea ce face ca acele câștiguri datorate rulării pe procesoare cu multe core-uri să fie limitate (dar totuși existente).
Concomitent cu paralelizarea în domeniul hardware, o mișcare similară - și poate de o amplitudine chiar mai mare - s-a întâmplat și în lumea software, conducând la creșterea sistemelor masiv distribuite. Sistemele distribuite erau cunoscute și folosite de multă vreme, pachete precum PVM sau MPI fiind deja înrădăcinate în domeniul academic și în cel al supercomputerelor. Dar abia creșterea companiilor din domeniul Internetului (în principal Google) a dus la popularizarea procesării distribuite la scară largă pe așa numitul commodity hardware. Înainte de schimbările aduse de cei de la Google, soluția oricărei organizații care avea nevoie de procesarea unui volum mare de date era să cumpere un mainframe extrem de scump. Marele avantaj al mainframe-urilor era că puteau face ca software-ul relativ normal să se execute repede și să proceseze cantități mari (după standardele vremii) de date.
În contrast, noua paradigmă permitea un prag de intrare mult mai scăzut din punctul de vedere al prețului pentru hardware - dar cu prețul modificării modului în care software-ul era proiectat. Iar popularizarea algoritmilor de consens distribuit (Paxos, two-phase commit, etc.) a dus la apariția unei căi de mijloc, și anume sistemele tranzacționale distribuite, care au permis o tranziție mai ușoară de la gândirea tranzacțională tradițională înspre gândirea nouă bazată pe eventual consistency.
În mod predictibil, pentru o schimbare de asemenea magnitudine, a durat destul de mult până noua paradigmă a fost acceptată la scară largă - dar în mod sigur în acest moment este acceptată. Chiar mai mult, se poate argumenta că avem de-a face cu o supra-acceptare - altfel spus, vedem noua paradigmă înlocuind-o pe cea veche chiar acolo unde nu este cazul.
Ultimul factor care a contribuit la situația curentă este presiunea venită de la un mediu de business aflat într-o continuă accelerare, mai ales în lumea online. Time-to-market contează mai mult decât orice, iar programatorii buni devin o resursă din ce în ce mai rară la nivel mondial. Aceste lucruri înseamnă o creștere constantă a riscurilor generate de opportunity cost și de costurile de dezvoltare. Această creștere face la rândul ei, mai atractivă o modalitate de a crește performanța în mod simplu, alocând mai mult hardware.
Având în vedere toți acești factori, e normal să apară impresia că procesarea paralelă, bazată pe scalare orizontală, este viitorul și că modul tradițional de a ridica nivelul performanței crescând performanța liniară este depășit. Altfel spus, că problemele de performanță se rezolvă alocând mai mult hardware, lucru ușor și ieftin. Și în mod sigur această impresie este parțial adevărată. Ce nu este adevărat este că orice problemă de performanță poate fi rezolvată în acest mod.
În mod surprinzător, sunt încă multe domenii unde performanța poate fi îmbunătățită doar făcând programele în sine mai eficiente. De notat că numărul acestor domenii este în creștere.
Cel mai evident aspect care trebuie menționat este că nu orice procesare este paralelizabilă. Destule probleme pot fi rezolvate doar în mod secvențial, din cauza limitelor teoretice ale gradului de paralelizare a unui program și a cantității mare de research în acest domeniu pornind de la legea lui Amdahl. De asemenea, chiar și în cazul problemelor rezolvabile prin paralelizare, există limitări introduse de eficiență: procesarea ineficientă poate în multe cazuri să ducă la costuri crescute de operare sau la un impact crescut asupra bateriei. La acestea se adaugă și faptul că există (tot mai) multe medii unde puterea de calcul disponibilă este limitată (mobile, embedded, etc.). În paragrafele următoare, vom trece pe scurt în revistă un număr de domenii unde performanța liniară este încă definitorie.
Vom începe cu domeniul burselor electronice, mai precis pe tranzacționarea automată pe aceste burse. În acest domeniu, paralelismul ajută într-o anumită măsură, însă beneficiile aduse sunt limitate. Caracteristica definitorie în tranzacționarea automată este latența scăzută, iar scalarea orizontală poate ajuta doar anumite puncte din lanțul citire-analiză-decizie-acțiune-scriere. Jucătorii din acest domeniu duc la extrem măsurile luate pentru scăderea latenței (implementări de algoritmi de tranzacționare în hardware, cabluri submarine instalate de fonduri de investiții, rețele de comunicație bazate pe microunde și pe lasere, etc.) ceea ce face extrem de costisitoare orice ineficiență la nivel de software.
Pe lângă piețele financiare, sistemele de tranzacționare automată se află (și sunt în creștere) într-un număr tot mai mare de sectoare industriale. Poate unii dintre cititori au fost puși în situația neplăcută de a pierde un bidding war în ultima zecime de secundă pe Ebay în fața unui profesionist folosind un sistem de bidding automat, sau poate au pus un pariu pe unul dintre multele site-uri care oferă burse de pariuri cu API-uri, care pot fi accesate de algoritmi automați de pariere. Mai puțin vizibile publicului larg, sistemele de tranzacționare automată devin coloana vertebrală a unor domenii precum industria publicitară (reclamele pe care le vedeți în browser sunt tranzacționate în timp real, în timp ce se încarcă pagina pe care o vizitați) sau logistica (există burse electronice unde transportatorii își dispută contractele pentru accesul la marfă, și viceversa). Toate acestea au aceleași cerințe ca și bursele financiare, latența scăzută fiind de o importanță capitală - deci scalarea nu poate compensa pentru software ineficient.
Apoi există coloșii numiți big data și machine learning. Pe de o parte, aceste domenii sunt emblematice pentru scalarea orizontală, dar pe de altă parte, costurile generate de procesarea cantităților enorme de date pot crește vertiginos dacă performanța este lăsată doar în seama hardware-ului și a scalării. Există diverse definiții despre ce înseamnă big data, dar o definiție foarte bună este că dacă nu te interesează cât te costă procesarea datelor tale înseamnă că nu ai big data. Acolo unde costul sau timpul de procesare doare, scalarea nu o să ajute.
Alte domenii în creștere accelerată unde scalarea nu este un panaceu sunt noua generație de platforme embedded - și anume IoT și wearable. Pe aceste platforme, performanța liniară este extrem de importantă datorită procesoarelor foarte simple (deci încete) și datorită bateriei limitate. E adevărat că anumite procesări pot fi mutate spre cloud, dar și aici există limite. Dacă un smart socket nu își poate face treaba pentru că nu merge conexiunea la Internet, nu o să mai pară foarte smart. Sau dacă acel socket consumă mai mult curent decât becul care e legat la el, și aceasta doar pentru a măsura cât curent consumă respectivul bec, în mod sigur nu o să fie văzut ca un produs de succes.
Același lucru se aplică încă platformelor embedded clasice, pornind de la automobile autonome și terminând cu mașini industriale sau device-uri banale precum să zicem un automat de cafea - cantități enorme de software se scriu în continuare pentru astfel de device-uri.
Mergând mai departe, aplicațiile mobile sunt în continuare un domeniu extrem de important, unde eficiența și performanța sunt critice din motive similare platformelor embedded - puterea de calcul limitată a procesoarelor (cel puțin în comparație cu procesoarele din calculatoare) și restricțiile impuse de durata de viață a bateriei. Ultimul lucru pe care un dezvoltator de programe mobile și-l dorește este ca aplicația sa să fie oprită de battery watchdog-ul sistemului de operare, și mai mult să fie și alertat utilizatorul despre acest lucru.
În continuare, domenii vaste precum dezvoltarea de jocuri sau dezvoltarea de software anti-malware (sau orice programe care trebuie să facă procesări în fundal), se extind pe noi platforme (vezi mai sus mobile, IoT, etc.) unde nevoia de performanță este chiar mai stringentă.
Mai departe, există cantități nemăsurabile de aplicații enterprise care nu vor fi niciodată reproiectate și rescrise pentru a beneficia de avantajele potențiale ale scalării orizontale - dar clienții vor continua să ceară îmbunătățiri de performanță.
Aplicațiile web sau cele bazate pe tehnologii web, (de exemplu Evernote, (ahem) PopcornTime sau lunga suită de aplicații bazate pe Electron) au părți semnificative care se execută fie în browser, fie într-un sandbox bazat pe un browser, făcând dificilă implementarea oricărui tip de scalare. Consider că majoritatea acestor strategii sunt extrem de ineficiente, și mai mult, în general, strategia creatorilor în domeniul performanței este pur și simplu de a ignora problema. Noi, dezvoltatorii de software, am reușit prin eforturi susținute să coborâm atât de mult așteptările utilizatorilor încât faptul că o aplicație de luat notițe are nevoie de zeci sau sute de megabytes și de o mașină performantă pentru a rula, e văzut ca un lucru normal.
Performanța este întotdeauna benefică, dar concentrarea eforturilor pentru a obține performanță ridicată nu este întotdeauna necesară.
Un prim motiv este faptul că compilatoarele, interpretoarele, mașinile virtuale, procesoarele și alte piese ale hardware-ului fac mari eforturi pentru a face programele să se execute rapid chiar dacă programatorul nu a depus eforturi semnificative în această direcție. Acest lucru aduce performanța la un nivel acceptabil într-un număr semnificativ de cazuri chiar fără o axare pe performanță din partea programatorului.
În al doilea rând, după cum am mai menționat, există cazuri în care alți factori (în general, time-to-market sau costurile de dezvoltare) pot să fie mai importanți decât performanța. Un sistem perfect care ajunge pe piață după ce fereastra de oportunitate s-a închis nu-și mai are rostul, și e mult mai rău decât un sistem imperfect ajuns pe piață în timp util.
Un al treilea motiv se referă la programele care au cerințe de performanță destul de lejere. În aceste cazuri, nu are absolut niciun rost să optimizezi un site web să răspundă în mai puțin de o milisecundă, având în vedere că timpul de reacție al unui om este de ordinul sutelor de milisecunde. În destule cazuri când performanța este necesară, ea poate fi de multe ori amânată. Knuth spunea cândva că premature optimization is the root of all evil. În mod sigur, există un sâmbure de adevăr în această idee.
Este foarte greu să estimezi câte dintre programele scrise în fiecare zi au nevoie de optimizări de performanță, câte dintre ele se pot baza pe scalare orizontală și câte, de fapt, nu au nevoie de niciuna dintre acestea. Toate aceste categorii de programe există și vor continua să existe în viitorul previzibil - și, precum am arătat, domeniile unde performanța liniară este importantă nu numai că nu dispar, ci devin tot mai pregnante.
Având în vedere acest lucru este la fel de important ca oricând ca un programator competent să poată să scrie când este nevoie cod eficient și performant. Este o abilitate de prim nivel pentru oricine care își dorește să fie văzut ca un programator senior care poate să abordeze diverse tipuri de proiecte. Lipsa acestei abilități poate fi văzută, de exemplu, ca și lipsa abilităților de proiectare și arhitectură a software-ului. Ca și design pattern-urile, aceste abilități trebuie să existe în portofoliul programatorului pentru a fi folosite în acele ocazii unde e nevoie de ele.
Pe de altă parte, este la fel de adevărat că nu orice programator va avea nevoie de astfel de abilități, la fel cum mulți programatori nu au nevoie de abilități arhitecturale - și aceasta deoarece arhitectura le va fi livrată de un senior developer. Pentru programatorii care își vor petrece întreaga carieră scriind site-uri web - și aceasta este o carieră foarte posibilă în zilele noastre - să învețe despre performanță poate fi o risipă de timp. În paranteză fie zis, același lucru este adevărat și despre a învăța cum să construiești software care poate scala orizontal.
Dar un programator versatil din zilele noastre are nevoie de aceste abilități la fel de mult ca oricând, și lumea software-ului va avea nevoie de aceste abilități ale programatorilor până când locul nostru va fi luat de inteligențe artificiale. Dar aceasta este cu totul o altă poveste.
de Ovidiu Mățan
de Ovidiu Mățan
de Ovidiu Mățan
de Ovidiu Mățan
de Ovidiu Mățan