Hogyan működik JavaScript: belül a V8-as motor + 5 tipp, hogyan kell írni optimalizált kód

Aug 21, 2017 · 11 perc olvassa el a

Pár hete elindítottunk egy sorozatot, amelynek célja ásni mélyebbre a JavaScript hogy is működik ez valójában azt hittük, hogy ismerjük az építőkövei a JavaScript hogy jönnek, hogy együtt játszanak, akkor képes lesz arra, hogy írjon jobb kódot, majd alkalmazások.,

a sorozat első bejegyzése a motor, a futásidő és a híváshalmaz áttekintésére összpontosított. Ez a második bejegyzés a Google V8 JavaScript motorjának belső részeibe merül. Mi is nyújt néhány gyors tipp, hogyan kell írni jobb JavaScript kód-legjobb gyakorlatok a fejlesztő csapat SessionStack következik, amikor az épület a termék.

áttekintés

a JavaScript motor olyan program vagy tolmács, amely JavaScript kódot hajt végre., A JavaScript motor lehet végrehajtani, mint egy szabványos tolmács, vagy just-in-time fordító, amely lefordítja a JavaScript bytecode valamilyen formában.,/li>

  • SpiderMonkey — az első JavaScript motor, amely akkoriban hajtott a Netscape Navigator, ma hatáskörök Firefox
  • JavaScriptCore — a nyílt forráskódú, kereskedelmi forgalomba, mint Nitro alakult ki az Apple a Safari
  • a POKER — KDE motor eredetileg által kifejlesztett Harri Porten a KDE projekt Konqueror böngésző
  • Csakra (JScript9) — Internet Explorer
  • Csakra (JavaScript) — A Microsoft Szélén
  • Nashorn, nyílt forráskódú részeként OpenJDK, írta Oracle Java Nyelven Eszköz Csoport
  • JerryScript — egy könnyű motor a neten a Dolgokat.,
  • miért jött létre a V8 motor?

    a Google által épített V8 motor nyílt forráskódú és C++nyelven íródott. Ezt a motort a Google Chrome-ban használják. A többi motortól eltérően azonban a V8-at a népszerű csomóponthoz is használják.js futási idő.

    V8-as volt az első célja, hogy növelje a teljesítményt a JavaScript-végrehajtás belső böngészők., A sebesség elérése érdekében a V8 tolmács használata helyett a JavaScript kódot hatékonyabb gépi kódra fordítja. A JavaScript kódot gépi kódra fordítja végrehajtáskor egy JIT (Just-In-Time) fordító végrehajtásával, mint sok modern JavaScript motor, például a SpiderMonkey vagy a Rhino (Mozilla). A fő különbség itt az, hogy a V8 nem termel bájtkódot vagy közbenső kódot.

    V8 korábban két fordító

    volt az 5. verzió előtt.,9 A V8 kijött (megjelent az év elején), a motor használt két fordítóprogramok:

    • teljes codegen-egy egyszerű, nagyon gyors fordító, hogy előállított egyszerű, viszonylag lassú gép kód.
    • főtengely-egy összetettebb (Just-In-Time) optimalizálása fordító előállított erősen optimalizált kódot.,l>
    • A fő szál nem az, amit elvár: hozza a kód össze, majd végrehajtani
    • van is egy külön szál összeállításáért, úgy, hogy a fő szál is tovább végrehajtó míg az előbbi optimalizálása a kódot
    • Egy Profilozó szál, amely megmondja, hogy a runtime, amely módszerek töltünk sok időt, így Főtengely lehet optimalizálni őket
    • Egy pár szál kezelni a Szemetes söpri

    Amikor először végrehajtó a JavaScript kód, V8-as kihasználja a teljes codegen, amely közvetlenül fordítja a értelmezi a JavaScript használatát a gép kód, anélkül, hogy bármilyen átalakítás., Ez lehetővé teszi, hogy nagyon gyorsan elindítsa a gépi kód végrehajtását. Vegye figyelembe, hogy a V8 nem használ közbenső bytecode ábrázolást, így eltávolítva a tolmács szükségességét.

    amikor a kód egy ideje fut, a profiler szál elegendő adatot gyűjtött ahhoz, hogy megmondja, melyik módszert kell optimalizálni.

    ezután a főtengely optimalizálása egy másik menetben kezdődik. Lefordítja a JavaScript absztrakt szintaxisfát egy magas szintű statikus egyfeladatos (ssa) reprezentációra, amit hidrogénnek neveznek, és megpróbálja optimalizálni a hidrogén gráfot. A legtöbb optimalizálás ezen a szinten történik.,

    Inlining

    az első optimalizálás a lehető legtöbb kód beillesztése. Az Inlining a híváshely (a kódsor, ahol a funkciót hívják) helyettesítésének folyamata a hívott funkció testével. Ez az egyszerű lépés lehetővé teszi a következő optimalizálás, hogy több értelmes.

    div>

    hidden class

    a JavaScript egy prototípus alapú nyelv: nincsenek osztályok és objektumok klónozási folyamattal jönnek létre., A JavaScript egy dinamikus programozási nyelv is, ami azt jelenti, hogy a tulajdonságok könnyen hozzáadhatók vagy eltávolíthatók egy objektumból az instantálás után.

    a legtöbb JavaScript tolmács szótárszerű struktúrákat (hash függvény alapú) használ az objektum tulajdonságértékek helyének tárolására a memóriában. Ez a struktúra teszi visszakeresése az érték egy tulajdonság JavaScript több számításigényesen drága, mint lenne egy nem dinamikus programozási nyelv, mint a Java vagy C#., A Java-ban az összes objektum tulajdonságot egy fix objektum elrendezés határozza meg az összeállítás előtt, és nem lehet dinamikusan hozzáadni vagy eltávolítani futásidőben (nos, A C# rendelkezik a dinamikus típussal, amely egy másik téma). Ennek eredményeként a Tulajdonságok (vagy ezeknek a tulajdonságoknak a mutatói) értékei folyamatos pufferként tárolhatók a memóriában, mindegyik között rögzített eltolással. Az eltolás hossza könnyen meghatározható a tulajdonság típusa alapján, míg ez nem lehetséges a JavaScriptben, ahol egy tulajdonságtípus változhat futási idő alatt.,

    mivel a szótárak használata az objektum tulajdonságainak a memóriában való megtalálásához nagyon nem hatékony, a V8 más módszert használ: rejtett osztályok. A rejtett osztályok hasonlóan működnek, mint a Java nyelvekben használt rögzített objektum-elrendezések (osztályok), kivéve, ha futási időben jönnek létre. Most nézzük meg, hogy valójában hogyan néznek ki:

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

    Miután az” új pont(1, 2) “meghívás megtörténik, a V8 létrehoz egy”C0” nevű rejtett osztályt.,

    a ponthoz még nem definiáltak tulajdonságokat, így a “C0” üres.

    Az első kijelentés után ” ez.x = x ” végrehajtásra kerül (a “pont” funkció belsejében), a V8 létrehoz egy második rejtett osztályt, a “C1” – et, amely a “C0″ – en alapul. A” C1 ” azt a helyet írja le a memóriában (az objektummutatóhoz viszonyítva), ahol az X tulajdonság megtalálható., Ebben az esetben az “x” eltolás 0-nál van tárolva, ami azt jelenti, hogy amikor egy pontobjektumot a memóriában folyamatos pufferként tekintünk meg, az első eltolás az “x”tulajdonságnak felel meg. A V8 frissíti a “C0” – et is egy “osztályátmenettel”, amely kimondja, hogy ha egy “x” tulajdonságot adunk egy pontobjektumhoz, a rejtett osztálynak “C0” – ről “C1” – re kell váltania. Az alábbi pontobjektum rejtett osztálya most “C1”.,

    hozzáadódik egy objektumhoz, a régi rejtett osztály frissül az új rejtett osztály átmeneti elérési útjával. A rejtett osztályátmenetek azért fontosak, mert lehetővé teszik a rejtett osztályok megosztását az azonos módon létrehozott objektumok között., Ha két objektum osztozik egy rejtett osztályban, és ugyanazt a tulajdonságot adják hozzá mindkettőhöz, az átmenetek biztosítják, hogy mindkét objektum ugyanazt az új rejtett osztályt kapja, valamint az összes optimalizált kódot, amely hozzá tartozik.

    Ez a folyamat megismétlődik, amikor a nyilatkozat ” Ez.y = y ” végrehajtásra kerül (ismét a Pontfüggvény belsejében, az “ez” után.x = x” utasítás).,

    egy új, “C2” nevű rejtett osztály jön létre, egy osztályátmenet kerül hozzáadásra a “C1” – hez, amely kimondja, hogy ha EGY “y” tulajdonság hozzáadódik egy Pontobjektumhoz (amely már tartalmaz “x” tulajdonságot), akkor a rejtett osztálynak “C2” – re kell váltania, és a pontobjektum rejtett osztálya “C2” – re frissül.

    a rejtett osztályátmenetek attól függnek, hogy milyen sorrendben adnak hozzá tulajdonságokat egy objektumhoz., Vessen egy pillantást az alábbi kódrészletre:

    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;

    most feltételezzük, hogy mind a P1, mind a p2 esetében ugyanazokat a rejtett osztályokat és átmeneteket használják. Nos, nem igazán. A ” p1 “esetében először az” a “tulajdonság kerül hozzáadásra, majd a”b” tulajdonság. A ” p2 “esetében azonban az első” b “van hozzárendelve, majd az”a”. Így a” p1 “és a” p2 ” különböző rejtett osztályokkal végződik a különböző átmeneti útvonalak eredményeként. Ilyen esetekben sokkal jobb a dinamikus tulajdonságok inicializálása ugyanabban a sorrendben, hogy a rejtett osztályok újra felhasználhatók legyenek.,

    Inline gyorsítótár

    V8 kihasználja egy másik technika optimalizálására dinamikusan gépelt nyelvek úgynevezett inline gyorsítótár. Az Inline gyorsítótárazás azon a megfigyelésen alapul, hogy ugyanazon módszer ismételt hívásai általában ugyanazon típusú objektumon fordulnak elő. Az inline gyorsítótár részletes magyarázata itt található.

    meg fogjuk érinteni az inline gyorsítótár általános fogalmát (abban az esetben, ha nincs ideje átmenni a fenti mélyreható magyarázaton).

    tehát hogyan működik?, A V8 fenntartja az objektumok típusának gyorsítótárát, amelyet paraméterként továbbítottak a legutóbbi módszerhívásokban, és ezt az információt arra használja, hogy feltételezze az objektum típusát, amelyet a jövőben paraméterként továbbítanak. Ha a V8 képes jó feltételezést tenni az objektum típusáról, amelyet átadnak egy módszernek, akkor megkerülheti az objektum tulajdonságainak megismerésének folyamatát, ehelyett használja a korábbi keresésekből származó tárolt információkat az objektum rejtett osztályába.

    tehát hogyan kapcsolódnak a rejtett osztályok és az inline gyorsítótár fogalmai?, Amikor egy módszert egy adott objektumra hívnak, a V8 motornak meg kell vizsgálnia az objektum rejtett osztályát annak érdekében, hogy meghatározza az adott tulajdonság eléréséhez szükséges eltolást. Ugyanazon módszer két sikeres hívása után ugyanahhoz a rejtett osztályhoz a V8 kihagyja a rejtett osztálykeresést, majd egyszerűen hozzáadja a tulajdonság eltolását az objektummutatóhoz. A módszer minden jövőbeli hívásakor a V8 motor feltételezi, hogy a rejtett osztály nem változott, és közvetlenül egy adott tulajdonság memóriacímére ugrik a korábbi keresésekből tárolt eltolások segítségével., Ez nagyban növeli a végrehajtási sebességet.

    az Inline gyorsítótár szintén az oka annak, hogy annyira fontos, hogy az azonos típusú objektumok megosszák a rejtett osztályokat. Ha létrehoz két objektum azonos típusú, de különböző rejtett osztályok (mint ahogyan a példa korábban), V8-as nem lesz képes használni inline cache, mert annak ellenére, hogy a két objektum azonos típusú, a megfelelő rejtett órákat rendelhet különböző ellensúlyozza, hogy a tulajdonságok parancsra.,

    a két objektum alapvetően a ugyanaz, de az “A” és “B” tulajdonságok különböző sorrendben jöttek létre.

    a Hidrogéngráf optimalizálása után a főtengely egy alacsonyabb szintű, Lítiumnak nevezett reprezentációra csökkenti. A lítium implementáció nagy része architektúrára jellemző. A nyilvántartási kiosztás ezen a szinten történik.,

    végül a lítium gépi kódba kerül. Aztán valami más történik, az úgynevezett OSR: on-stack csere. Mielőtt elkezdtünk volna összeállítani és optimalizálni egy nyilvánvalóan hosszú távú módszert, valószínűleg futtattuk. A V8 nem fogja elfelejteni, amit csak lassan hajt végre, hogy újra elinduljon az optimalizált verzióval. Ehelyett átalakítja az összes kontextust (verem, regiszterek), hogy a végrehajtás közepén át tudjuk váltani az optimalizált verzióra. Ez egy nagyon összetett feladat, szem előtt tartva, hogy más optimalizálások között a V8 kezdetben beillesztette a kódot., A V8 nem az egyetlen motor, amely képes erre.

    vannak biztosítékok úgynevezett deoptimization, hogy az ellenkező átalakulás, majd visszatér a nem optimalizált kódot, ha egy feltételezés, hogy a motor készült, nem érvényes többé.

    szemétgyűjtés

    szemétgyűjtéshez a V8 hagyományos generációs mark-and-sweep megközelítést alkalmaz a régi generáció tisztítására. A jelölési fázisnak meg kell állítania a JavaScript végrehajtását., Annak érdekében, hogy ellenőrizzék GC költségek, valamint a végrehajtás stabilabb, V8-as használ egyedi jelölés: séta helyett az egész halom, próbálja mark minden lehetséges tárgy, csak járni része a kupac, akkor folytatódik a program. A következő GC megálló folytatódik, ahonnan az előző heap séta megállt. Ez lehetővé teszi a nagyon rövid szüneteket a normál végrehajtás során. Mint korábban említettük, a sweep fázist külön szálak kezelik.

    gyújtás és turbófeltöltés

    a V8 5.9 2017. korábbi kiadásával új végrehajtási csővezetéket vezettek be., Ez az új csővezeték még nagyobb teljesítményfejlesztést és jelentős memóriamegtakarítást eredményez a valós JavaScript alkalmazásokban.

    az új végrehajtási csővezeték a gyújtás, a V8 tolmácsa, valamint a Turbofan, a V8 legújabb optimalizáló fordítója tetejére épül.

    a V8 csapat blogbejegyzését a témáról itt tekintheti meg.

    az 5. verzió óta.,9 a V8 kijött, teljes codegen és főtengely (a technológiák, amelyek szolgált V8 óta 2010) már nem használják a V8 JavaScript végrehajtás, mint a V8 csapat küzdött, hogy lépést tartson az új JavaScript nyelvi funkciók és az optimalizálás szükséges ezeket a funkciókat.

    Ez azt jelenti, hogy a teljes V8-nak sokkal egyszerűbb és fenntarthatóbb architektúrája lesz.

    fejlesztések az interneten és a csomóponton.,js benchmarks

    ezek a fejlesztések csak a kezdet. Az új gyújtás-és Turbófan csővezeték előkészíti az utat a további optimalizálásokhoz, amelyek növelik a JavaScript teljesítményét, és csökkentik a V8 lábnyomát mind a Chrome-ban, mind a Node-ban.js az elkövetkező években.

    Végül itt van néhány tipp és trükk, hogyan kell írni jól optimalizált, jobb JavaScript., Könnyen származnak ezek a tartalom felett, azonban itt egy összefoglaló az ön kényelmét:

    , Hogyan kell írni optimalizált JavaScript

    1. Rend objektum tulajdonságok: mindig létrehozni a tárgy tulajdonságai, ugyanabban a sorrendben, úgy, hogy a rejtett osztályok, majd ezt követően optimalizált kód, lehet osztani.
    2. dinamikus tulajdonságok: a Tulajdonságok hozzáadása egy objektumhoz az instantálás után egy rejtett osztályváltozást kényszerít, és lelassítja az előző rejtett osztályra optimalizált módszereket. Ehelyett hozzárendelje az objektum összes tulajdonságát a konstruktorához.,
    3. Methods: az ugyanazt a módszert többször végrehajtó kód gyorsabban fut, mint a sok különböző módszert csak egyszer végrehajtó kód (az inline gyorsítótár miatt).
    4. tömbök: kerülje el a ritka tömböket, ahol a kulcsok nem növekményes számok. Ritka tömbök, amelyek nem rendelkeznek minden elem bennük egy hash asztal. Az ilyen tömbök elemei drágábbak a hozzáféréshez. Próbálja meg elkerülni a nagy tömbök előzetes elosztását is. Jobb, ha növekszik, ahogy megy. Végül ne törölje az elemeket a tömbökben. Ez teszi a kulcsokat ritka.
    5. Tagged values: a V8 32 bites objektumokat és számokat jelöl., Ez használ egy kicsit tudni, hogy ez egy objektum (flag = 1) vagy egész (flag = 0) nevű SMI (kis egész), mert a 31 bit. Ezután, ha egy numerikus érték nagyobb, mint 31 bit, a V8 dobozolja a számot, duplává alakítva, majd létrehozva egy új objektumot a szám behelyezéséhez. Próbáljon meg 31 bites aláírt számokat használni, amikor csak lehetséges, hogy elkerülje a drága boksz műveletet egy JS objektumba.

    mi, a SessionStack-nál megpróbáljuk követni ezeket a legjobb gyakorlatokat a magasan optimalizált JavaScript kód írásakor., Ennek az az oka, hogy ha egyszer integrálja a sessionstack-ot a termelési webes alkalmazásba, mindent rögzít: minden DOM változás, felhasználói interakciók, JavaScript kivételek, veremnyomok, sikertelen hálózati kérések, hibakeresési üzenetek.
    A SessionStack segítségével a webes alkalmazásokban megjelenő problémákat videóként is lejátszhatja, és mindent megnézhet, ami a felhasználóval történt. És mindez kell történnie nincs teljesítmény hatása a web app.
    van egy ingyenes terv,amely lehetővé teszi, hogy az induláshoz ingyen.

    Share

    Vélemény, hozzászólás?

    Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük