Cum JavaScript funcționează: în interiorul motor V8 + 5 sfaturi despre cum să scrie cod optimizat

Aug 21, 2017 · 11 min de citit

Acum câteva săptămâni am început o serie menită să sapi mai adânc în JavaScript și cum funcționează de fapt: ne-am gândit că prin cunoașterea blocurile de JavaScript și cum au ajuns să se joace împreună, va fi capabil să scrie cod mai bun și aplicații.,

primul post al seriei s-a concentrat pe furnizarea unei imagini de ansamblu a motorului, a timpului de rulare și a stivei de apeluri. Această a doua postare va fi scufundată în părțile interne ale motorului JavaScript V8 al Google. Vom oferi, de asemenea, câteva sfaturi rapide despre cum să scrieți un cod JavaScript mai bun —Cele mai bune practici echipa noastră de dezvoltare de la SessionStack urmează atunci când construiește produsul.

Prezentare generală

Un motor JavaScript este un program sau un interpret care execută codul JavaScript., Un motor JavaScript poate fi implementat ca un interpret standard sau compilator just-in-time care compilează JavaScript în bytecode într-o anumită formă.,/li>

  • SpiderMonkey — primul motor JavaScript, care în zilele alimentat Netscape Navigator, și astăzi puterile Firefox
  • JavaScriptCore — open source, comercializate ca Nitro și dezvoltat de Apple pentru Safari
  • KJS — KDE motor dezvoltat inițial de către Harri Porten pentru proiectul KDE e Konqueror browser-ul web
  • Chakra (JScript9) — Internet Explorer
  • Chakra (JavaScript) — Microsoft Edge
  • Nashorn, open source, ca parte a OpenJDK, scris de Oracle Java Limbi și Instrument de Grup
  • JerryScript — este un motor ușor, pentru Internet de Lucruri.,
  • De ce a fost creat motorul V8?

    motorul V8 care este construit de Google este open source și scris în c++. Acest motor este utilizat în Google Chrome. Spre deosebire de restul motoarelor, V8 este folosit și pentru nodul popular.JS runtime.

    V8 a fost în primul rând proiectat pentru a crește performanța JavaScript executarea în interiorul browsere web., Pentru a obține viteză, V8 traduce codul JavaScript într-un cod de mașină mai eficient în loc să folosească un interpretor. Compilează codul JavaScript în codul mașinii la execuție prin implementarea unui compilator JIT (Just-In-Time), cum ar fi o mulțime de motoare JavaScript moderne, cum ar fi SpiderMonkey sau Rhino (Mozilla). Principala diferență aici este că V8 nu produce bytecode sau orice cod intermediar.

    V8 folosit pentru a avea două compilatoare

    înainte de versiunea 5.,9 din V8 a ieșit (lansat la începutul acestui an), motorul a folosit două compilatoare:

    • full-codegen — un compilator simplu și foarte rapid care a produs un cod de mașină simplu și relativ lent.
    • Arbore Cotit-un compilator de optimizare mai complex (Just-In-Time) care a produs un cod extrem de optimizat.,l>
    • firul principal face ceea ce v-ați aștepta: adu-ți cod, compila și apoi executa
    • Există, de asemenea, un fir separat pentru elaborarea, astfel încât firul principal poate ține de executare în timp ce prima este optimizarea codului
    • Un Profiler fir care va spune runtime pe metodele care ne petrecem o mulțime de timp, astfel încât arborele Cotit poate optimiza le
    • câteva fire să se ocupe de Gunoi Colector mătură

    atunci Când primul a executa cod JavaScript, V8 foloseste full-codegen, care se traduce direct de analizat JavaScript în cod mașină, fără nici o transformare., Acest lucru îi permite să înceapă să execute codul mașinii foarte rapid. Rețineți că V8 nu utilizează reprezentarea bytecode intermediare în acest fel eliminând necesitatea unui interpretor.

    când codul dvs. a rulat de ceva timp, firul profiler a adunat suficiente date pentru a spune ce metodă ar trebui optimizată.apoi, optimizările arborelui cotit încep într-un alt fir. Traduce arborele de sintaxă abstractă JavaScript într-o reprezentare statică de nivel înalt cu o singură atribuire (SSA) numită hidrogen și încearcă să optimizeze acel grafic de hidrogen. Cele mai multe optimizări se fac la acest nivel.,

    inline

    prima optimizare este inline cod cât mai mult posibil în avans. Inlining este procesul de înlocuire a unui site de apel (linia de cod în care se numește funcția) cu corpul funcției numite. Acest pas simplu permite ca următoarele optimizări să fie mai semnificative.

    Ascuns de clasă

    JavaScript este un prototip bazat pe limba: nu există clase și obiecte sunt create folosind un proces de clonare., JavaScript este, de asemenea, un limbaj de programare dinamic, ceea ce înseamnă că proprietățile pot fi ușor adăugate sau eliminate dintr-un obiect după instantierea acestuia.

    majoritatea interpreților JavaScript folosesc structuri asemănătoare dicționarului (bazate pe funcția hash) pentru a stoca locația valorilor proprietății obiectului în memorie. Această structură face preluarea valoarea unei proprietăți în JavaScript calcul mai scump decât ar fi într-un non-dinamic limbaj de programare cum ar fi Java sau C#., În Java, toate proprietățile obiectului sunt determinate de un aspect obiect fix înainte de compilare și nu pot fi adăugate dinamic sau eliminate în timpul rulării (bine, C# are tipul dinamic, care este un alt subiect). Ca rezultat, valorile proprietăților (sau indicii pentru acele proprietăți) pot fi stocate ca un tampon continuu în memorie cu un decalaj fix între fiecare. Lungimea unui offset poate fi ușor determinată în funcție de tipul de proprietate, în timp ce acest lucru nu este posibil în JavaScript unde un tip de proprietate se poate schimba în timpul rulării.,deoarece utilizarea dicționarelor pentru a găsi locația proprietăților obiectului în memorie este foarte ineficientă, V8 folosește în schimb o metodă diferită: clase ascunse. Clasele ascunse funcționează similar cu layout-urile de obiecte fixe (clase) utilizate în limbi precum Java, cu excepția faptului că sunt create în timpul rulării. Acum, să vedem cum arată de fapt:

    function Point(x, y) {
    this.x = x;
    this.y = y;
    }var p1 = new Point(1, 2);

    odată ce se întâmplă invocarea” new Point(1, 2)”, V8 va crea o clasă ascunsă numită”C0″.,

    – Nu proprietati au fost definite pentru Moment încă, deci „C0” este gol.

    odată ce prima afirmație ” acest lucru.x = x” este executat (în interiorul funcției „punct”), V8 va crea o a doua clasă ascunsă numită” C1 „care se bazează pe”C0”. „C1″ descrie locația din memorie (în raport cu indicatorul obiectului) unde poate fi găsită proprietatea X., În acest caz,” x „este stocat la offset 0, ceea ce înseamnă că atunci când vizualizați un obiect punct în memorie ca tampon continuu, primul offset va corespunde proprietății”x”. V8 va actualiza ,de asemenea,” C0 „cu o” tranziție de clasă „care afirmă că, dacă o proprietate” x „este adăugată la un obiect punct, clasa ascunsă ar trebui să treacă de la” C0 „la”C1”. Clasa ascunsă pentru obiectul punct de mai jos este acum „C1”.,

    de Fiecare dată când o nouă proprietate este adăugat la un obiect, vechea clasă de ascuns este actualizat cu o cale de tranziție la noua clasă de ascuns. Tranzițiile de clasă ascunse sunt importante deoarece permit ca clasele ascunse să fie partajate între obiecte create în același mod., Dacă două obiecte împărtășesc o clasă ascunsă și aceeași proprietate este adăugată la ambele, tranzițiile se vor asigura că ambele obiecte primesc aceeași nouă clasă ascunsă și tot codul optimizat care vine cu ea.

    Acest proces se repetă atunci când declarația „asta.y = y” este executat (din nou, în interiorul funcției punct, după „acest.x = x”).,

    o nouă clasă ascunsă numită „C2” este creată, o tranziție de clasă este adăugată la „C1” afirmând că dacă o proprietate „y” este adăugată la un obiect punct (care conține deja proprietatea „x”), atunci clasa ascunsă ar trebui să se schimbe la „C2”, iar clasa ascunsă a obiectului punct este actualizată la „C2”.

    Ascunse clasa tranziții sunt dependente de ordinea în care proprietățile sunt adăugate la un obiect., Aruncați o privire la fragmentul de cod de mai jos:

    function Point(x, y) {
    this.x = x;
    this.y = y;
    }var p1 = new Point(1, 2);
    p1.a = 5;
    p1.b = 6;var p2 = new Point(3, 4);
    p2.b = 7;
    p2.a = 8;

    acum, ați presupune că atât pentru p1, cât și pentru p2 vor fi utilizate aceleași clase și tranziții ascunse. Ei bine, nu chiar. Pentru „p1″, mai întâi se va adăuga proprietatea” a „și apoi proprietatea”b”. Pentru „p2″, cu toate acestea, primul” b „este atribuit, urmat de”a”. Astfel, „p1″ și ” p2 ” se termină cu diferite clase ascunse ca urmare a diferitelor căi de tranziție. În astfel de cazuri, este mult mai bine să inițializați proprietățile dinamice în aceeași ordine, astfel încât clasele ascunse să poată fi reutilizate.,

    caching Inline

    V8 profită de o altă tehnică pentru optimizarea limbajelor tastate dinamic numite caching inline. Cache-ul Inline se bazează pe observația că apelurile repetate către aceeași metodă tind să apară pe același tip de obiect. O explicație aprofundată a cache-ului inline poate fi găsită aici.

    vom atinge conceptul general de caching inline (în cazul în care nu aveți timp pentru a trece prin explicația în profunzime de mai sus).deci ,cum funcționează?, V8 menține o memorie cache a tipului de obiecte care au fost transmise ca parametru în apelurile recente ale metodei și utilizează aceste informații pentru a face o presupunere despre tipul de obiect care va fi transmis ca parametru în viitor. Dacă V8 este capabil să facă o presupunere bună despre tipul de obiect care va fi trecut la o metodă, poate ocoli procesul de a afla cum să accesați proprietățile obiectului și, în schimb, să utilizați informațiile stocate din căutările anterioare la clasa ascunsă a obiectului.deci, cum sunt legate conceptele de clase ascunse și cache-ul inline?, Ori de câte ori o metodă este apelată pe un anumit obiect, motorul V8 trebuie să efectueze o căutare la clasa ascunsă a acelui obiect pentru a determina compensarea pentru accesarea unei proprietăți specifice. După două apeluri reușite ale aceleiași metode către aceeași clasă ascunsă, V8 omite căutarea clasei ascunse și adaugă pur și simplu compensarea proprietății la indicatorul obiect în sine. Pentru toate apelurile viitoare ale acestei metode, motorul V8 presupune că clasa ascunsă nu s-a schimbat și sare direct în adresa de memorie pentru o anumită proprietate folosind compensările stocate din căutările anterioare., Acest lucru crește foarte mult viteza de execuție.

    cache-ul Inline este, de asemenea, motivul pentru care este atât de important ca obiectele de același tip să împartă clase ascunse. Dacă creați două obiecte de același tip și cu clase ascunse diferite (așa cum am făcut în exemplul anterior), V8 nu va putea utiliza cache-ul inline, deoarece, chiar dacă cele două obiecte sunt de același tip, clasele lor ascunse corespunzătoare atribuie diferite compensări proprietăților lor.,

    Cele două obiecte sunt în esență aceleași, dar „a” și „b” proprietățile au fost create într-o ordine diferită.

    Compilatie de cod mașină

    Odată ce Hidrogenul grafic este optimizat, arbore Cotit scade la un nivel inferior de reprezentare numit de Litiu. Cea mai mare parte a implementării litiului este specifică arhitecturii. Alocarea registrului se întâmplă la acest nivel.,în final, litiul este compilat în codul mașinii. Apoi se întâmplă altceva numit OSR: înlocuire on-stack. Înainte de a începe compilarea și optimizarea unei metode evident de lungă durată, probabil că o rulam. V8 nu va uita ceea ce tocmai a executat încet pentru a începe din nou cu versiunea optimizată. În schimb, va transforma tot contextul pe care îl avem (stivă, registre), astfel încât să putem trece la versiunea optimizată în mijlocul execuției. Aceasta este o sarcină foarte complexă, având în vedere că, printre alte optimizări, V8 a încorporat inițial codul., V8 nu este singurul motor capabil să o facă.

    există garanții numite deoptimizare pentru a face transformarea opusă și revine la codul neoptimizat în cazul în care o presupunere a motorului nu mai este adevărată.pentru colectarea gunoiului, V8 folosește o abordare tradițională generațională a marcării și măturării pentru a curăța vechea generație. Faza de marcare ar trebui să oprească execuția JavaScript., Pentru a controla costurile GC și a face execuția mai stabilă, V8 folosește marcarea incrementală: în loc să parcurgă întreaga grămadă, încercând să marcheze fiecare obiect posibil, merge doar o parte din grămadă, apoi reia execuția normală. Următoarea oprire GC va continua de unde s-a oprit plimbarea heap anterioară. Acest lucru permite pauze foarte scurte în timpul execuției normale. Așa cum am menționat anterior, faza de măturare este tratată de fire separate.

    aprindere și TurboFan

    odată cu lansarea V8 5.9 mai devreme în 2017, a fost introdusă o nouă conductă de execuție., Această nouă conductă realizează îmbunătățiri și mai mari ale performanței și economii semnificative de memorie în aplicațiile JavaScript din lumea reală.

    noua conductă de execuție este construită pe Ignition, interpretorul V8 și TurboFan, cel mai nou compilator de optimizare al V8.

    puteți consulta postarea de pe blog de la echipa V8 despre acest subiect aici.

    de la versiunea 5.,9 din V8-a ieșit, full-codegen și arborele Cotit (tehnologii care au servit V8 din 2010) nu au mai fost folosite de V8 de execuție JavaScript ca V8 echipa a luptat pentru a ține pasul cu noile limba JavaScript caracteristici și optimizări necesare pentru aceste caracteristici.acest lucru înseamnă că în general V8 va avea arhitectura mult mai simplu și mai întreținute merge mai departe.

    Îmbunătățiri pe Web și Nod.,js referință

    Aceste îmbunătățiri sunt doar începutul. Noua conductă de aprindere și TurboFan deschide calea pentru optimizări suplimentare care vor spori performanța JavaScript și vor micșora amprenta V8 atât în Chrome, cât și în nod.js în următorii ani.în cele din urmă, iată câteva sfaturi și trucuri despre cum să scrieți JavaScript bine optimizat, mai bun., Puteți obține cu ușurință acestea din conținutul de mai sus, totuși, iată un rezumat pentru confortul dvs.:

    cum se scrie JavaScript optimizat

    1. ordinea proprietăților obiectului: instantiați întotdeauna proprietățile obiectului în aceeași ordine, astfel încât clasele ascunse și codul optimizat ulterior să poată fi partajate.
    2. proprietăți dinamice: adăugarea de proprietăți la un obiect după instanțiere va forța o schimbare de clasă ascunsă și va încetini orice metode optimizate pentru clasa ascunsă anterioară. În schimb, atribuiți toate proprietățile unui obiect în constructorul său.,
    3. metode: codul care execută aceeași metodă în mod repetat va rula mai repede decât codul care execută multe metode diferite o singură dată (datorită cache-ului inline).
    4. matrice: evitați matrice rare în cazul în care cheile nu sunt numere incrementale. Matrice rare care nu au fiecare element în interiorul lor sunt un tabel hash. Elementele din astfel de tablouri sunt mai scumpe de accesat. De asemenea, încercați să evitați pre-alocarea matricelor mari. Este mai bine să crești pe măsură ce mergi. În cele din urmă, nu ștergeți elementele din matrice. Face ca cheile să fie rare.
    5. valori etichetate: V8 reprezintă obiecte și numere cu 32 de biți., Folosește un pic pentru a ști dacă este un obiect (flag = 1) sau un număr întreg (flag = 0) numit SMI (small Integer) din cauza celor 31 de biți ai săi. Apoi, dacă o valoare numerică este mai mare de 31 de biți, V8 va caseta numărul, transformându-l într-un dublu și creând un obiect nou pentru a pune numărul în interior. Încercați să utilizați 31 de biți numere semnate ori de câte ori este posibil, pentru a evita operațiunea de box scump într-un obiect JS.noi, cei de la SessionStack, încercăm să urmăm aceste bune practici în scrierea unui cod JavaScript extrem de optimizat., Motivul este că, odată ce integrați SessionStack în aplicația web de producție, începe să înregistreze totul: toate modificările DOM, interacțiunile utilizatorilor, excepțiile JavaScript, urmele de stivă, solicitările de rețea eșuate și mesajele de depanare.
      cu SessionStack, puteți reda problemele din aplicațiile web sub formă de videoclipuri și puteți vedea tot ce s-a întâmplat cu utilizatorul dvs. Și toate acestea trebuie să se întâmple fără impact asupra performanței aplicației dvs. web.
      există un plan gratuit care vă permite să începeți gratuit.

    Share

    Lasă un răspuns

    Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *