Jak JavaScript funguje: uvnitř V8 + 5 tipů, jak psát optimalizovaný kód

Aug 21, 2017 · 11 min číst

Před pár týdny jsme začali sérii zaměřené na kopání hlouběji do Javascriptu a jak to vlastně funguje: mysleli jsme si, že tím, že zná stavební bloky Javascriptu a jak přijdou společně hrát budete moci psát lepší kód a aplikace.,

první příspěvek série se zaměřil na poskytování přehledu o motoru, běhu a zásobníku hovorů. Tento druhý příspěvek se ponoří do vnitřních částí javascriptového motoru Google V8. Poskytneme také několik rychlých tipů, jak napsat lepší kód JavaScriptu —osvědčené postupy, které náš vývojový tým v Sessionstacku sleduje při vytváření produktu.

přehled

JavaScript engine je program nebo interpret, který provádí JavaScript kód., JavaScript engine může být implementován jako standardní interpret, nebo just-in-time kompilátor, který kompiluje JavaScript do bytecode v nějaké formě.,/li>

  • SpiderMonkey — první JavaScript engine, který v dobách powered Netscape Navigator, a dnes pravomocí Firefox
  • JavaScriptCore — open source, trh jako Nitro a vyvinutý společností Apple pro Safari
  • KJS — KDE je motor původně vyvinut Harri Porten pro projekt KDE je Konqueror webový prohlížeč
  • Čakra (JScript9) — Internet Explorer
  • Čakra (JavaScript) — Microsoft Edge
  • Nashorn, open source jako součást OpenJDK, napsal Oracle, Java, Jazyky a Nástroje, Skupiny
  • JerryScript — je lehký motor pro Internet Věcí.,
  • proč byl vytvořen motor V8?

    motor V8, který je postaven společností Google, je open source a napsaný v C++. Tento motor se používá uvnitř prohlížeče Google Chrome. Na rozdíl od ostatních motorů se však V8 používá také pro populární uzel.js runtime.

    V8 byl poprvé navržen tak, aby zvýšení výkonnosti JavaScript provedení uvnitř webových prohlížečů., Aby bylo možné získat rychlost, V8 překládá kód JavaScript do efektivnějšího strojového kódu namísto použití tlumočníka. Při provádění kompiluje kód JavaScript do strojového kódu implementací kompilátoru JIT (Just-In-Time), jako mnoho moderních javascriptových motorů, jako je SpiderMonkey nebo Rhino (Mozilla). Hlavní rozdíl je v tom, že V8 nevytváří bytekód ani žádný mezilehlý kód.

    V8 měl před verzí 5 dva kompilátory

    .,9 V8 vyšel (vydáno na začátku tohoto roku), motor použity dva kompilátory:

    • full-codegen — jednoduchý a velmi rychlý kompilátor, který produkoval jednoduchý a relativně pomalý strojový kód.
    • Klikový hřídel – složitější (Just-In-Time) optimalizace kompilátor, který produkoval vysoce optimalizovaný kód.,l>
    • hlavní vlákno dělá to, co byste očekávat, že: načtení kódu, zkompilovat a poté spustit.
    • k Dispozici je také samostatné vlákno pro sestavování, tak, že hlavní vlákno může udržet provedení, zatímco bývalý je optimalizace kódu
    • Profiler vlákno, které bude vyprávět runtime, na kterém metod trávíme hodně času tak, že Hřídel může optimalizovat
    • několik vláken zpracovat Garbage Collector tažení

    Při prvním spuštění JavaScript kódu, V8 využívá full-codegen, které se přímo překládá analyzovat JavaScript do strojového kódu, bez jakékoli transformace., To umožňuje spustit spuštění strojového kódu velmi rychle. Všimněte si, že V8 nepoužívá intermediatecode reprezentace tímto způsobem odstranění potřeby tlumočníka.

    když váš kód běží nějakou dobu, vlákno profileru shromáždilo dostatek dat, aby zjistilo, která metoda by měla být optimalizována.

    Další optimalizace klikového hřídele začíná v jiném závitu. Překládá JavaScript abstraktní syntaxe strom na vysoké úrovni statické single-přiřazení (SSA) reprezentace s názvem vodík a snaží se optimalizovat, že vodík graf. Většina optimalizace se provádí na této úrovni.,

    Inlining

    první optimalizace vkládá co nejvíce kódu předem. Inlining je proces nahrazení místa volání (řádek kódu, kde se funkce nazývá) tělem volané funkce. Tento jednoduchý krok umožňuje, aby následující optimalizace byly smysluplnější.

    Skryté třídy

    JavaScript je prototyp-založené jazyk: neexistují třídy a objekty jsou vytvořeny pomocí klonování proces., JavaScript je také dynamický programovací jazyk, což znamená, že vlastnosti mohou být snadno přidány nebo odstraněny z objektu po jeho instance.

    Většina interpret Javascriptu používat slovník-jako struktury (hash funkce založené) uložte umístění objektu hodnoty v paměti. Tato struktura umožňuje načítání hodnotu vlastnosti v Javascriptu více výpočetně dražší, než by být v non-dynamický programovací jazyk, jako je Java nebo C#., V Java, všechny vlastnosti objektu jsou určeny na pevný objekt layout, než kompilace a nemůže být dynamicky přidány nebo odstraněny v běhu (no, C# má dynamický typ, který je jiné téma). V důsledku toho mohou být hodnoty vlastností (nebo ukazatele na tyto vlastnosti) uloženy jako kontinuální vyrovnávací paměť v paměti s pevným posunem mezi každým. Délku ofsetu lze snadno určit na základě typu vlastnosti, zatímco to není možné v JavaScriptu, kde se Typ vlastnosti může během běhu změnit.,

    vzhledem k tomu, že použití slovníků k nalezení umístění vlastností objektů v paměti je velmi neefektivní, V8 místo toho používá jinou metodu: skryté třídy. Skryté třídy fungují podobně jako pevné rozvržení objektů (třídy) používané v jazycích, jako je Java, kromě toho, že jsou vytvořeny za běhu. Nyní se podívejme, jak skutečně vypadají:

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

    jakmile dojde k vyvolání“ nového bodu(1, 2)“, V8 vytvoří skrytou třídu nazvanou“C0″.,

    Žádné vlastnosti byly definovány pro Bod, přesto, takže „C0“ je prázdný.

    po prvním prohlášení “ toto.X = x“ se provádí (uvnitř funkce „Point“), V8 vytvoří druhou skrytou třídu nazvanou“ C1″, která je založena na“C0″. „C1″ popisuje umístění v paměti (vzhledem k ukazateli objektu), kde lze najít vlastnost x., V tomto případě je“ x „uloženo v offsetu 0, což znamená, že při prohlížení bodového objektu v paměti jako spojité vyrovnávací paměti bude první offset odpovídat vlastnosti“x“. V8 také aktualizuje „C0“ s „přechodem třídy“, který uvádí, že pokud je do bodového objektu přidána vlastnost „x“, skrytá třída by se měla přepnout z „C0″na “ C1″. Skrytá třída pro bodový objekt níže je nyní „C1“.,

    Pokaždé, když nová vlastnost je přidán k objektu, staré skryté class je aktualizován s přechodem cestu k nové skryté třídy. Skryté přechody tříd jsou důležité, protože umožňují sdílení skrytých tříd mezi objekty, které jsou vytvořeny stejným způsobem., Pokud dva objekty sdílejí skrytou třídu a do obou z nich je přidána stejná vlastnost, přechody zajistí, že oba objekty obdrží stejnou novou skrytou třídu a veškerý optimalizovaný kód, který s ní přichází.

    tento proces se opakuje, když příkaz “ toto.y = y“ se provádí (opět uvnitř bodové funkce, po “ toto.x = x “ prohlášení).,

    nový skrytý třídy s názvem „C2“ je vytvořen, třída přechod je přidán do „C1“, uvádí, že je-li vlastnost „y“ je přidán do Bodu objektu (který již obsahuje vlastnost „x“), pak skryté třídy by se měl změnit na „C2“, a bod objekt je skrytý class je aktualizován na „C2“.

    Skryté třídy přechody jsou závislé na pořadí, ve kterém vlastnosti jsou přidány do objektu., Podívejte se na fragment kódu níže:

    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;

    Nyní, by se předpokládat, že pro oba p1 a p2 stejný skryté třídy a přechody budou použity. No, vlastně ne. Pro “ p1 „bude nejprve přidána vlastnost“ a „a poté vlastnost“b“. Pro “ p2 „je však přiřazeno první“ b“, následované“a“. Takže „p1″ a “ p2 “ skončí s různými skrytými třídami v důsledku různých přechodových cest. V takových případech je mnohem lepší inicializovat dynamické vlastnosti ve stejném pořadí, aby mohly být skryté třídy znovu použity.,

    inline caching

    V8 využívá další techniky pro optimalizaci dynamicky psaných jazyků zvaných inline caching. Inline caching se opírá o pozorování, že opakované volání ke stejné metodě mají tendenci se vyskytovat na stejném typu objektu. Podrobné vysvětlení inline ukládání do mezipaměti najdete zde.

    dotkneme se obecné koncepce inline cachingu (v případě, že nemáte čas projít výše uvedeným podrobným vysvětlením).

    tak jak to funguje?, V8 si udržuje cache typ objektů, které byly předány jako parametr v posledních volání metody a používá tyto informace, aby se předpoklad o typu objektu, který bude předán jako parametr v budoucnu. Pokud V8 je schopen udělat dobrý předpoklad o typu objektu, který bude předán do metody, to lze obejít proces přijít na to, jak se přístup k vlastnosti objektu, a místo toho použít uložené informace z předchozích vyhledávání na objektu skryté.třídy.

    Jak tedy souvisí pojmy skryté třídy a inline ukládání do mezipaměti?, Kdykoli je metoda vyvolána na konkrétním objektu, motor V8 musí provést vyhledávání do skryté třídy tohoto objektu, aby určil posun pro přístup k určité vlastnosti. Po dvou úspěšných hovorech stejné metody do stejné skryté třídy V8 vynechá skryté vyhledávání třídy a jednoduše přidá posun vlastnosti k ukazateli objektu. Pro všechna budoucí volání této metody předpokládá motor V8, že se skrytá třída nezměnila, a skočí přímo do adresy paměti pro konkrétní vlastnost pomocí kompenzací uložených z předchozích vyhledávání., To výrazně zvyšuje rychlost provádění.

    inline caching je také důvodem, proč je tak důležité, aby objekty stejného typu sdílely skryté třídy. Pokud vytvoříte dva objekty stejného typu a s různými skryté třídy (jako jsme to udělali v příkladu výše), V8 nebude moci používat inline ukládání do mezipaměti, protože i když dva objekty jsou stejného typu, jejich odpovídající skryté tříd přiřadit různé offsety jejich vlastnosti.,

    dva objekty jsou v podstatě stejné, ale na „a“ a „b“ vlastnosti byly vytvořeny v jiném pořadí.

    Compilation to machine code

    jakmile je graf vodíku optimalizován, Klikový hřídel jej snižuje na nižší úroveň reprezentace zvané Lithium. Většina implementace lithia je specifická pro architekturu. Registrace alokace se děje na této úrovni.,

    nakonec je Lithium kompilováno do strojového kódu. Pak se stane něco jiného s názvem OSR: výměna na zásobníku. Než jsme začali sestavovat a optimalizovat zjevně dlouhodobou metodu, pravděpodobně jsme ji spustili. V8 nebude zapomenout na to, co to jen pomalu provádí začít znovu s optimalizovanou verzí. Místo toho transformuje veškerý kontext, který máme (zásobník, registry), abychom mohli uprostřed provádění přepnout na optimalizovanou verzi. Jedná se o velmi složitý úkol, který má na paměti, že mimo jiné optimalizace V8 původně vložil kód., V8 není jediný motor, který to dokáže.

    Tam jsou záruky, tzv. deoptimization, aby se opačné transformace a vrátí se zpět do non-optimalizovaný kód v případě, předpokládáme-li, že motor nedrží pravda.

    sběr odpadků

    pro sběr odpadků používá V8 tradiční generační přístup značky a zametání k čištění staré generace. Fáze značení má zastavit provádění JavaScriptu., Za účelem kontroly GC náklady a provedení více stabilní, V8 používá inkrementální značení: místo chůze celé haldy, se snaží označit každý možný předmět, bude chodit pouze část haldy, poté se opět obnoví normální provedení. Další zastávka GC bude pokračovat od místa, kde se zastavila předchozí halda. To umožňuje velmi krátké pauzy během normálního provádění. Jak již bylo zmíněno dříve, fáze zametání je řešena samostatnými vlákny.

    zapalování a TurboFan

    s uvolněním V8 5.9 dříve v roce 2017 bylo zavedeno nové provedení potrubí., Toto nové potrubí dosahuje ještě větších vylepšení výkonu a významných úspor paměti v aplikacích JavaScriptu v reálném světě.

    nové provedení potrubí je postaven na vrcholu zapalování, V8 tlumočník, a TurboFan, V8 je nejnovější optimalizační kompilátor.

    můžete se podívat na blogový příspěvek týmu V8 o tématu zde.

    od verze 5.,9 V8 vyšel, full-codegen a Klikového hřídele (technologie, které sloužily V8 od roku 2010) se již používá V8 pro provádění Javascriptu jako V8 tým se snažil držet krok s nové funkce jazyka JavaScript a optimalizace potřebných pro tyto funkce.

    to znamená, že celkový V8 bude mít mnohem jednodušší a udržovatelnější architekturu.

    Vylepšení na Webu a Uzel.,js benchmarky

    tato vylepšení jsou jen začátkem. Nové potrubí zapalování a TurboFan připravuje cestu pro další optimalizace, které zvýší výkon JavaScriptu a zmenší stopu V8 v Chromu i uzlu.js v příštích letech.

    konečně, zde je několik tipů a triků, jak psát dobře optimalizovaný, lepší JavaScript., Můžete snadno odvodit z výše uvedeného obsahu, nicméně, zde je souhrn pro vaše pohodlí:

    Jak psát optimalizovaný JavaScript

    1. Příkaz vlastnosti objektu: vždy vytvořit instanci své vlastnosti objektu ve stejném pořadí tak, že skryté tříd, a následně optimalizovaný kód, mohou být sdíleny.
    2. dynamické vlastnosti: přidání vlastností objektu po instanci vynutí skrytou změnu třídy a zpomalí všechny metody, které byly optimalizovány pro předchozí skrytou třídu. Místo toho přiřaďte všechny vlastnosti objektu v jeho konstruktoru.,metody
    3. : kód, který stejnou metodu provádí opakovaně, poběží rychleji než kód, který provádí mnoho různých metod pouze jednou (kvůli inline cache).
    4. pole: vyhněte se řídkým polím, kde klíče nejsou přírůstková čísla. Řídká pole, která nemají každý prvek uvnitř nich jsou hash tabulky. Prvky v těchto polích jsou dražší přístup. Také se snažte vyhnout předběžnému přidělení velkých polí. Je lepší růst, jak jdete. Nakonec neodstraňujte prvky v polích. Díky tomu jsou klíče řídké.
    5. označené hodnoty: V8 představuje objekty a čísla s 32 bity., Používá trochu vědět, jestli je to objekt (flag = 1) nebo celé číslo (flag = 0) volal SMI (malé celé číslo), protože jeho 31 bitů. Pak, pokud je číselná hodnota větší než 31 bitů, V8 bude box číslo, soustružení do dvojitého a vytvoření nového objektu, aby číslo uvnitř. Pokuste se použít 31 bitových podepsaných čísel, kdykoli je to možné, abyste se vyhnuli drahé operaci boxu do objektu JS.

    my v SessionStack se snažíme dodržovat tyto osvědčené postupy při psaní vysoce optimalizovaného kódu JavaScript., Důvodem je, že jakmile integrujete SessionStack do produkční webové aplikace, začne nahrávat vše: všechny změny DOM, interakce uživatelů, výjimky JavaScript, stopy stacku, neúspěšné síťové požadavky a ladicí zprávy.
    S SessionStack můžete přehrávat problémy ve svých webových aplikacích jako videa a vidět vše, co se stalo vašemu uživateli. A to vše se musí stát bez vlivu výkonu pro Vaši webovou aplikaci.
    existuje bezplatný plán, který vám umožní začít zdarma.

    Share

    Napsat komentář

    Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *