de Balaganesh Damodaran
Clasa Array JavaScript expune destul de multe metode (filtru, hartă, reducere), care iterează printr-o matrice și apelează o funcție iterator pentru a efectua acțiuni pe matrice. Înlănțuirea acestor metode vă permite să scrieți un cod curat, ușor de citit. Dar ce ne costă această comoditate în ceea ce privește performanța și merită?
Javascript este un limbaj „funcțional”. Ceea ce înseamnă acest lucru este că funcțiile sunt obiecte de primă clasă în Javascript și, ca atare, pot fi transmise ca parametri altor funcții. Există destul de multe metode încorporate furnizate de biblioteca standard Javascript, care folosește acest fapt pentru a ne permite să scriem cod curat, ușor de înțeles și ușor de citit.
Conţinut
Metode încorporate de matrice Javascript și înlănțuire
O astfel de clasă încorporată care folosește pe scară largă natura funcțională a Javascript este Array
clasă. Array
s în Javascript expune o serie de metode de instanță, care:
- acceptați o funcție ca argument,
- iterați pe matrice,
- și apelați funcția, trecând de-a lungul elementului matricei ca parametru la funcție.
Cele mai populare dintre acestea sunt desigur forEach
, filter
, map
și reduce
. Deoarece unele dintre aceste metode returnează și Array
ca valoare de returnare a metodei, acestea sunt adesea înlănțuite astfel:
const tripExpenses = [{ amount: 12.07, currency: 'USD', paid: true}, { amount: 1.12, currency: 'USD', paid: true}, { amount: 112.00, currency: 'INR', paid: false}, { amount: 54.17, currency: 'USD', paid: true}, { amount: 16.50, currency: 'USD', paid: true}, { amount: 189.50, currency: 'INR', paid: false}];
const totalPaidExpensesInINR = tripExpenses .filter(expense => expense.paid) .map(expense => { if(expense.currency == 'USD') return expense.amount * 70; else return expense.amount; }) .reduce((amountA, amountB) => amountA + amountB);
În acest exemplu, calculăm cheltuielile plătite totale după ce le-am convertit din USD în INR. Pentru a face acest lucru, suntem:
-
filter
ingtripExpenses
să extragă doar cheltuielile plătite, -
map
ping suma cheltuielilor din moneda specificată și convertirea acesteia în INR și -
reduce
sumele INR pentru a obține suma.
Pare un caz de utilizare obișnuit, foarte tipic, valid pentru înlănțuirea metodelor matrice nu? O mulțime de dezvoltatori cărora li s-a învățat să scrie Javascript funcțional ar ieși cu ceva similar atunci când li se va cere să rezolve această problemă.
Problema cu Înlănțuirea Metodei Array
În prezent, tripExpenses
matricea are doar 6 articole, deci este relativ rapid. Dar ce se întâmplă atunci când trebuie să analizăm cheltuielile de călătorie pentru, să zicem, o întreagă companie de angajați pentru întregul exercițiu financiar și a noastră tripExpenses
matricea începe să aibă sute de mii de elemente?
Datorită JSPerf, putem vizualiza acest cost destul de ușor. Deci, să rulați un test de comparație pentru același cod cu tripExpenses
având 10 elemente, 10.000 de elemente și 100.000 de elemente. Iată rezultatul Comparație JSPerf:
Graficul arată numărul de operații pe secundă, iar mai mare este mai bine. În timp ce mă așteptam ca cazul de 100.000 de elemente să nu aibă performanțe, chiar nu mă așteptam ca cazul de 10.000 de elemente să aibă performanțe slabe. Deoarece nu este vizibil cu adevărat pe grafic, să analizăm numerele:
- 10 Elemente – 6.142.739 operații pe secundă
- 10.000 de elemente – 2.199 operații pe secundă
- 100.000 de elemente – 223 operațiuni pe secundă
Da, e foarte rău! Și, în timp ce procesarea unui set de 100.000 de elemente s-ar putea să nu se întâmple des, 10.000 de elemente este un caz de utilizare foarte plauzibil pe care l-am văzut în mod regulat în mai multe aplicații pe care le-am dezvoltat (mai ales pe partea serverului).
Acest lucru ne arată că atunci când scriem – chiar și ceea ce pare a fi un cod destul de simplu – ar trebui să fim cu atenție la orice problemă de performanță care ar putea apărea din cauza modului în care scriem codul nostru.
Dacă, în loc să înlănțuie filter
, map
și reduce
împreună, ne rescriem codul astfel încât toată munca să fie realizată în linie, într-o singură buclă, să putem obține performanțe semnificativ mai bune.
let totalPaidExpensesInINR = 0;
for(let expense of tripExpenses){ if(expense.paid){ if(expense.currency == 'USD') totalPaidExpensesInINR += (expense.amount * 70); else totalPaidExpensesInINR += expense.amount; }}
Să alergăm altul Comparație JSPerf pentru a vedea cum funcționează acest lucru împotriva omologului său funcțional, într-un test de 10.000 de elemente:
După cum puteți vedea, pe Chrome (și prin extensie Node.JS), exemplul funcțional este cu 77% mai lent decât exemplul for-of. Pe Firefox, numerele sunt mult mai apropiate, dar exemplul funcțional este încă cu 16% mai lent decât exemplul for-of.
De ce o deltă de performanță atât de mare?
Deci, de ce este exemplul funcțional mult mai lent decât exemplul de exemplu? Ei bine, este o combinație de factori, dar factorii principali pe care, în calitate de dezvoltator, îi putem controla de pe teritoriul utilizatorilor sunt:
- Buclarea asupra acelorși elemente de matrice de mai multe ori.
- Cheltuielile generale ale funcției solicită fiecare iterație din exemplul funcțional.
Dacă vedeți exemplul de exemplu, veți vedea că vom itera numai prin tripExpenses
matrice o dată. De asemenea, nu apelăm funcții din interior, ci efectuăm calculele noastre în linie.
Una dintre marile „câștiguri” de performanță pe care le obțin motoarele Javascript moderne este integrarea apelurilor funcționale. Ceea ce înseamnă acest lucru este că motorul vă va compila codul într-o versiune în care compilatorul înlocuiește apelul funcției, cu funcția în sine (adică în linie unde apelați funcția). Acest lucru elimină cheltuielile generale de apelare a funcției și oferă câștiguri imense de performanță.
Cu toate acestea, nu putem spune întotdeauna cu certitudine dacă un motor Javascript va alege să integreze sau nu o funcție, astfel încât să o facem singuri ne asigură că avem cea mai bună performanță posibilă.
Concluzie
Unii dezvoltatori pot considera că exemplul de mai jos este mai puțin lizibil și mai greu de înțeles decât exemplul funcțional. Pentru acest exemplu particular, aș spune că ambele stiluri sunt la fel de lizibile. Cu toate acestea, în cazul exemplului funcțional, confortul înlănțuirii metodei tinde să ascundă multiplele iterații și apeluri de funcții de la dezvoltator, făcând astfel mai ușor pentru un dezvoltator neexperimentat să scrie cod neperformant.
Nu spun că ar trebui să evitați întotdeauna modul funcțional – sunt sigur că există o mulțime de cazuri valabile pentru utilizarea modului funcțional și pentru înlănțuirea metodelor. Dar o regulă generală de reținut care trebuie amintită atunci când vine vorba de performanță și iterarea matricilor în Javascript este că, dacă înlănțuiți metode care iterează prin întreaga matrice, probabil că ar trebui să vă opriți și să luați în considerare impactul performanței înainte de a merge mai departe.
Mi-ar plăcea să vă aud părerea despre ceea ce am scris în acest articol. Cântă cu comentariile tale de mai jos.
[Feb 6th, 2019] Unele avertismente și lucruri de reținut, așa cum au subliniat comentatorii
La fel de Evidențiat de Paul B, există o lovitură de performanță atunci când se folosește `pentru … of` într-o formă transpilată în browsere, dar puteți folosi oricând o buclă normală cu o variabilă iterator pentru a evita acest lucru. Cu toate acestea, așa cum spune Paul, există destul de multe avantaje în a rămâne cu o funcție de iterare. Mergi să citești comentariul lui, este demn să fii un articol singur.
În plus, o mulțime de oameni au spus, de asemenea, că aceasta ar fi o optimizare prematură sau o micro-optimizare și sunt parțial de acord cu ei. În general, ar trebui să optimizați întotdeauna pentru lizibilitate și mentenabilitate față de performanță, până în momentul în care performanța slabă începe să vă afecteze. Odată ce ați atins acest punct, vă recomandăm să vă reconsiderați iteratorii.
Publicat inițial la asleepysamurai.com pe 8 ianuarie 2019.
#Ferițivă #înlănțuirea #metodelor #matrice #în #JavaScript
Feriți-vă de înlănțuirea metodelor de matrice în JavaScript