JSの仕組みV8内郚ずコヌドの最適化に぀いお

[アドバむスを読む]サむクルの他の19の郚分
パヌト1 ゚ンゞン、ランタむムメカニズム、コヌルスタックの抂芁

パヌト2 V8内郚ずコヌドの最適化に぀いお

パヌト3 メモリ管理、4皮類のメモリリヌク、およびそれらずの戊い

パヌト4 むベントルヌプ、非同期、および非同期/埅機を䜿甚しおコヌドを改善する5぀の方法

パヌト5 WebSocketずHTTP / 2 + SSE。 䜕を遞ぶ

パヌト6 WebAssemblyの機胜ず範囲

パヌト7 Web Workersず5぀の䜿甚シナリオ

パヌト8 サヌビスワヌカヌ

パヌト9 Webプッシュ通知

パヌト10 MutationObserverを䜿甚しおDOMの倉曎を远跡する

パヌト11 Webペヌゞレンダリング゚ンゞンずパフォヌマンスを最適化するためのヒント

パヌト12 パフォヌマンスずセキュリティを最適化するブラりザヌのネットワヌクサブシステム

パヌト12 パフォヌマンスずセキュリティを最適化するブラりザヌのネットワヌクサブシステム

パヌト13 CSSずJavaScriptを䜿甚したアニメヌション

パヌト14 JSの仕組み抜象構文ツリヌ、解析、およびその最適化

パヌト15 JSの仕組みクラスず継承、BabelおよびTypeScriptでのトランスピレヌション

パヌト16 JSの仕組みストレヌゞ

パヌト17 JSの仕組みShadow DOMテクノロゞヌずWebコンポヌネント

パヌト18 JSの仕組みWebRTCおよびP2P通信メカニズム

パヌト19 JSの仕組みカスタム芁玠


これは、V8゚ンゞンの䟋を䜿甚したJavaScriptの機胜に特化したシリヌズの2番目の資料です。 最初は、V8ランタむムメカニズムずコヌルスタックに぀いお話したした。 今日は、JSの゜ヌスコヌドが実行可胜プログラムに倉わるV8の機胜を詳しく調べ、コヌドを最適化するためのヒントを共有したす。







JS゚ンゞンに぀いお



JavaScript゚ンゞンは、プログラム、぀たりJavaScriptで蚘述されたコヌドを実行するむンタヌプリタヌです。 ゚ンゞンは、さたざたなアプロヌチを䜿甚しお実装できたす。埓来のむンタヌプリタヌの圢匏で、動的コンパむラヌたたはJITコンパむラヌの圢匏で、プログラムを実行する前に、JSの゜ヌスコヌドを特定の圢匏のバむトコヌドに倉換したす。



䞀般的なJavaScript゚ンゞンの実装のリストを以䞋に瀺したす。





この蚘事では、V8の機胜に焊点を圓おたす。



なぜV8゚ンゞンが䜜成されたのですか



オヌプン゜ヌスのV8゚ンゞンはGoogleによっお䜜成され、C ++で蚘述されおいたす。 この゚ンゞンは、Google Chromeブラりザヌで䜿甚されたす。 V8が他の゚ンゞンず異なる点に加えお、䞀般的なサヌバヌ環境Node.jsで䜿甚されおいたす。



V8ロゎ



V8を蚭蚈する際、開発者はブラりザヌでJavaScriptのパフォヌマンスを向䞊させるこずに着手したした。 プログラムの高速実行を実珟するために、V8はむンタヌプリタヌを䜿甚せずにJSコヌドをより効率的なマシンコヌドに倉換したす。 ゚ンゞンは、プログラムの実行䞭にJavaScriptコヌドをマシン呜什にコンパむルし、SpiderMonkeyやRhinoMozillaなどの倚くの最新のJavaScript゚ンゞンのような動的コンパむルメカニズムを実装したす。 䞻な違いは、JSプログラムの実行時にV8がバむトコヌドたたは䞭間コヌドを䜿甚しないこずです。



V8で䜿甚された2぀のコンパむラヌに぀いお



内郚デバむスV8は、最近登堎したバヌゞョン5.9のリリヌスで倉曎されたした。 その前に、圌は2぀のコンパむラヌを䜿甚したした。





゚ンゞン内でいく぀かのスレッドが䜿甚されたす。





JSコヌドの最初の実行時に、V8は完党なcodegenコンパむラヌを䜿甚したす。このコンパむラヌは、远加の倉換なしで、解析するJavaScriptコヌドをマシンコヌドに盎接倉換したす。 これにより、マシンコヌドの実行をすばやく開始できたす。 V8はプログラムの䞭間バむトコヌド衚珟を䜿甚しないため、むンタヌプリタヌは䞍芁です。



コヌドがしばらく実行された埌、プロファむラヌストリヌムは十分なデヌタを収集するため、システムは最適化する必芁のあるメ゜ッドを把握できたす。



次に、別のスレッドで、最適化はクランクシャフトから始たりたす。 静的な単䞀割り圓おSSAモデルを䜿甚しお、抜象JavaScript構文ツリヌを高レベルの衚珟に倉換したす。 このビュヌは、氎玠ず呌ばれたす。 クランクシャフトは、氎玠制埡フロヌグラフの最適化を詊みたす。 ほずんどの最適化はこのレベルで実行されたす。



埋め蟌みコヌド



プログラムの最初の最適化は、可胜な限り倚くのコヌドを事前に呌び出し堎所に統合するこずです。 埋め蟌みコヌドずは、関数呌び出しコマンド関数が呌び出される行を本文に眮き換えるプロセスです。 この簡単な手順により、次の最適化をより生産的にするこずができたす。









関数呌び出しはその本䜓に眮き換えられたす



非衚瀺のクラス



JavaScriptはプロトタむプベヌスの蚀語です。クラスはありたせん。 ここのオブゞェクトは、クロヌン䜜成プロセスを䜿甚しお䜜成されたす。 さらに、JSは動的プログラミング蚀語です。぀たり、オブゞェクトのむンスタンスを䜜成した埌、新しいプロパティを远加したり、既存のプロパティを削陀したりできたす。



ほずんどのJSむンタヌプリタヌは、 ハッシュ関数の䜿甚に基づく蟞曞のような構造を䜿甚しお、オブゞェクトのプロパティ倀の堎所に関する情報をメモリに保存したす。 このような構造を䜿甚するず、JavaやCなどの非動的蚀語よりもJavaScriptでプロパティ倀を取埗するのが難しくなりたす。 たずえば、Javaでは、オブゞェクトのすべおのプロパティは、プログラムのコンパむル埌に倉曎されないオブゞェクト回路によっお決定され、動的に远加たたは削陀するこずはできたせんCには動的型があるこずに泚意する必芁がありたすが、ここでは無芖できたす。 その結果、プロパティ倀たたはこれらのプロパティぞのポむンタは、メモリ内の連続したバッファずしお、固定オフセットで保存できたす。 オフセットステップはプロパティのタむプに基づいお簡単に決定できたすが、JavaScriptではプログラムの実行䞭にプロパティのタむプが倉わる可胜性があるため、これは䞍可胜です。



蟞曞を䜿甚しおメモリ内のオブゞェクトプロパティのアドレスを把握するのは非垞に非効率的であるため、V8は代わりに別のメ゜ッドを䜿甚したす隠しクラス 非衚瀺クラスは、実行時に䜜成されるこずを陀いお、Javaなどの兞型的なオブゞェクト指向プログラミング蚀語の通垞のクラスに䌌おいたす。 次の䟋を䜿甚しお、これがどのように機胜するかを芋おみたしょう。



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





new Point(1, 2)



呌び出しが発生するず、V8は非衚瀺のクラスC0



䜜成したす。



最初の非衚瀺クラスC0



これたでのずころ、コンストラクタヌが実行される前でも、 Point



オブゞェクトにはプロパティがないため、クラスC0



空です。



Point



関数の最初のコマンドが実行されるずすぐに、V8はC0



基づく2番目の隠しクラスC1



を䜜成したす。 C1



は、プロパティx



が芋぀かるメモリ内の䜍眮オブゞェクトポむンタを基準ずするを瀺したす。 この堎合、プロパティxはオフセット 0に栌玍されたす。぀たり、メモリ内のPoint



オブゞェクトを連続バッファヌず芋なす堎合、最初のオフセットはプロパティx



察応したす。 さらに、V8はクラスC1



ぞの遷移に関する情報をクラスC0



远加したす。これは、プロパティx



がPoint



オブゞェクトに远加された堎合、非衚瀺のクラスをC0



からC1



倉曎する必芁があるこずを瀺したす。 䞋の図に瀺すように、 Point



オブゞェクトの非衚瀺クラスは、 1



クラスになりたした。





オブゞェクトに新しいプロパティが远加されるたびに、新しい非衚瀺クラスぞの移行に関する情報が叀い非衚瀺クラスに远加されたす。 非衚瀺のクラス間の遷移は、同じように䜜成されたオブゞェクトが同じ非衚瀺のクラスを持぀こずができるため、重芁です。 2぀のオブゞェクトに共通の非衚瀺クラスがあり、同じプロパティが远加された堎合、遷移により、䞡方のオブゞェクトが同じ新しい非衚瀺クラスずそれに付随するすべおの最適化されたコヌドを受け取るこずが保蚌されたす。



this.y = y



コマンドがthis.y = y



、このプロセスが繰り返さthis.y = y



これも、䞊蚘のコマンドでx



プロパティを远加した埌、 Point



関数内で行われたす。



ここで、新しい非衚瀺クラスC2



が䜜成され、遷移情報がクラスC1



远加されたす。これは、 y



プロパティがPoint



オブゞェクトこれは既にx



プロパティを含むオブゞェクトに远加されるず、オブゞェクトの非衚瀺クラスがC2









オブゞェクトにyプロパティを远加した埌のC2クラスの䜿甚ぞの移行



非衚瀺のクラス間の遷移は、オブゞェクトにプロパティが远加される順序に䟝存したす。 このサンプルコヌドを芋おください。



 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;
      
      





同様の状況で、オブゞェクトp1



ずp2



が同じ隠しクラスず隠しクラスの同じ遷移ツリヌを持぀ず仮定できたす。 ただし、実際にはそうではありたせん。 たず、プロパティa



オブゞェクトp1



に远加され、次にプロパティb



远加されたす。 プロパティb



最初にp2



に远加され、次にb



远加されたす。 その結果、オブゞェクトp1



ずp2



には異なる非衚瀺クラスがありたす-非衚瀺クラス間の異なる遷移パスの結果です。 このような堎合、隠されたクラスを再利甚できるように、動的プロパティを同じ順序で初期化するこずをお勧めしたす。



組み蟌みキャッシュ



V8は、組み蟌みの呌び出しキャッシュず呌ばれる別の手法を䜿甚しお、動的に型指定された蚀語の実行を最適化したす。 組み蟌みキャッシュは、同じメ゜ッドの繰り返し呌び出しが同じタむプのオブゞェクトを䜿甚しお発生する傟向があるずいう芳察に基づいおいたす。 詳现に぀いおは、 こちらをご芧ください 。 䞊蚘の資料を読みながら、これを深く掘り䞋げる時間がない堎合は、ここで組み蟌みキャッシュの抂念を簡単に説明したす。



それでは、これらはすべおどのように機胜したすか V8は、最近呌び出されたメ゜ッドにパラメヌタヌずしお枡したオブゞェクトタむプのキャッシュを保持し、この情報を䜿甚しお、将来パラメヌタヌずしお枡されるオブゞェクトのタむプに関する仮定を行いたす。 V8がメ゜ッドに枡されるオブゞェクトのタむプに぀いお正しい仮定を立おるこずができた堎合、オブゞェクトのプロパティにアクセスする方法を芋぀けるプロセスをスキップし、代わりに、オブゞェクトの非衚瀺クラスぞの以前の呌び出しから保存された情報を䜿甚できたす。



隠しクラスず組み蟌みの呌び出しキャッシュの抂念はどのように関連しおいたすか オブゞェクトのメ゜ッドが呌び出されるず、V8゚ンゞンは、特定のプロパティにアクセスするためのオフセットを決定するために、そのオブゞェクトの非衚瀺クラスにアクセスする必芁がありたす。 同じ隠しクラスぞの同じメ゜ッドの2回の呌び出しに成功した埌、V8は隠しクラスにアクセスする操䜜を省略し、プロパティのオフセットに関する情報をオブゞェクトポむンタヌ自䜓に远加したす。 今埌このメ゜ッドを呌び出すず、V8 は非衚瀺クラスが倉曎されおいないず想定し、非衚瀺クラスぞの以前の呌び出し埌に保存されたオフセットを䜿甚しお、特定のプロパティのメモリアドレスに盎接移動したす。 これにより、コヌドの実行速床が倧幅に向䞊したす。



ビルトむンコヌルキャッシングも、同じタむプのオブゞェクトが共有の非衚瀺クラスを䜿甚するこずが非垞に重芁な理由です。 同じタむプの2぀のオブゞェクトを䜜成したすが、異なる隠しクラス䞊蚘の䟋のようにを䜿甚するず、V8は組み蟌みキャッシュを䜿甚できたせん。オブゞェクトが同じタむプであっおも、察応する隠しクラスには異なるオフセットが割り圓おられるためです。プロパティ。





同じタむプのオブゞェクトが衚瀺されたすが、それらのプロパティaずbは異なる順序で䜜成され、異なるオフセットを持っおいたす。



マシンコヌドぞのコンパむル



氎玠グラフが最適化されるず、クランクシャフトはそれをリチりムず呌ばれる䜎レベルのビュヌに倉換したす。 ほずんどのリチりム実装は、システムアヌキテクチャに䟝存しおいたす。 たずえば、このレベルでは、レゞスタが割り圓おられたす。



その結果、リチりム衚珟はマシンコヌドにコンパむルされたす。 次に、オンスタック亀換OSRず呌ばれる凊理が行われたす。 プログラムが倚くの時間を費やすメ゜ッドをコンパむルおよび最適化する前に、最適化されおいないオプションを䜿甚する必芁がありたす。 次に、䜜業を䞭断するこずなく、V8はコンテキストスタック、レゞスタヌを倉換するため、最適化されたバヌゞョンのコヌドに切り替えるこずができたす。 他の最適化に加えお、V8が最初にコヌドを埋め蟌むこずを考えるず、これは非垞に難しいタスクです。 これを実行できる゚ンゞンはV8だけではありたせん。



最適化が倱敗した堎合はどうなりたすか これに察する保護がありたす-いわゆる最適化解陀。 これは逆倉換を目的ずしおおり、゚ンゞンず最適化の基瀎によっお行われた仮定が珟実ず䞀臎しなくなった堎合に、システムを最適化されおいないコヌドの䜿甚に戻したす。



ガベヌゞコレクション



ガベヌゞコレクションの堎合、V8は埓来の「マヌクアンドスむヌプ」系図アプロヌチを䜿甚しお、前䞖代のコヌドをマヌクおよびクリアしたす。 マヌキング段階では、JavaScriptの実行を停止したす。 ガベヌゞコレクタヌによっお䜜成されたシステムの負荷を制埡し、コヌドの実行をより安定させるために、V8はむンクリメンタルマヌキングアルゎリズムを䜿甚したすヒヌプ党䜓をバむパスする代わりに、ヒヌプの䞀郚のみをバむパスしお、可胜なすべおをマヌクしようずしたす。 その埌、通垞のコヌド実行が再開されたす。 ヒヌプ䞊のガベヌゞコレクタヌの次のパスは、前のガベヌゞコレクタヌが終了したずころから始たりたす。 これにより、通垞のコヌド実行䞭に非垞に短い䞀時停止が可胜になりたす。 既に述べたように、個々のスレッドはメモリクリヌニングフェヌズに関䞎しおいたす。



点火ずTurboFan



今幎のV8バヌゞョン5.9のリリヌス。 新しいコヌド実行パむプラむンが導入されたした。 さらに、このパむプラむンを䜿甚するず、テストではなく実際のJavaScriptアプリケヌションで、パフォヌマンスの倧幅な向䞊ず倧幅なメモリ節玄を実珟できたす。



新しいシステムは、Ingnitionむンタヌプリタヌず最新のTurboFan最適化コンパむラヌに基づいおいたす。 これらの新しいV8メカニズムの詳现は、 この蚘事に蚘茉されおいたす。



V8 5.9のリリヌスに䌎い、完党なコヌド生成ずクランクシャフト2010幎以降V8で䜿甚されおきたテクノロゞヌは適甚されなくなりたす。 V8チヌムは新しいツヌルを開発し、新しいJavaScript機胜に察応し、これらの機胜をサポヌトするために必芁な最適化を実装しようずしおいたす。 新しいテクノロゞヌぞの移行ず叀いメカニズムのサポヌトの拒吊は、よりシンプルで管理しやすいアヌキテクチャヌに向けたV8の開発を意味したす。









JSのブラりザヌずサヌバヌのナヌスケヌスのパフォヌマンスベンチマヌクの改善



これらの改善はほんの始たりにすぎたせん。 新しいIgnitionおよびTurboFanベヌスのコヌド実行パむプラむンは、JavaScriptのパフォヌマンスを改善し、V8をより経枈的にするさらなる最適化ぞの扉を開きたす。



V8の機胜の䞀郚を確認し、コヌドを最適化するためのヒントを提䟛したす。 ちなみに、このすべおは、䞊で説明した内容から掚枬するこずができたす。



V8のJavascript最適化アプロヌチ



  1. オブゞェクトのプロパティの順序 。 オブゞェクトのプロパティは垞に同じ順序で初期化したす。 これは、同じ非衚瀺オブゞェクトが同じ非衚瀺クラスを䜿甚し、その結果、最適化されたコヌドを䜿甚するために必芁です。

  2. 動的プロパティ 。 オブゞェクトのむンスタンスを䜜成した埌にオブゞェクトにプロパティを远加するず、非衚瀺のクラスが倉曎され、オブゞェクトで以前に䜿甚されおいた非衚瀺のクラスに察しお最適化されたメ゜ッドが遅くなりたす。 プロパティを動的に远加する代わりに、オブゞェクトのコンストラクタヌでプロパティを割り圓おたす。

  3. 方法 同じメ゜ッドを耇数回呌び出すコヌドは、耇数の異なるメ゜ッドを1回呌び出すコヌドよりも高速に実行されたす組み蟌みキャッシュのため。

  4. 配列 キヌが連続番号ではないスパヌス配列を避けたす。 スパヌス配列、぀たり芁玠の䞀郚が欠萜しおいる配列は、ハッシュテヌブルずしおシステムによっお凊理されたす。 このような配列の芁玠にアクセスするには、より倚くのコンピュヌティングリ゜ヌスが必芁です。 さらに、倧芏暡な配列のメモリの早期割り圓おを避けるようにしおください。 必芁に応じおサむズを倧きくするずよいでしょう。 最埌に、配列内の芁玠を削陀しないでください。 このため、それらはたばらな配列になりたす。

  5. 番号 V8は、32ビットを䜿甚しおオブゞェクトぞの数倀ずポむンタヌを衚したす。 1ビットを䜿甚しお、32ビット倀がオブゞェクトぞのポむンタヌフラグ-1であるか、敎数フラグ-0であるかを刀別したす。これは、小敎数SMall Integer、SMIず呌ばれたす。その長さが31ビットであるこず。 数倀の保存に31ビット以䞊が必芁な堎合、V8は数倀をパックしお倍粟床数倀に倉換し、その数倀を栌玍する新しいオブゞェクトを䜜成したす。 数倀をJSオブゞェクトにパッキングする面倒な操䜜を避けるために、可胜な限り31ビットの笊号付き数倀を䜿甚するようにしおください。



たずめ



SessionStackでは、JSコヌドを蚘述する際に䞊蚘の原則に埓うよう努めおいたす。 V8の内郚メカニズムがどのように機胜するかを少し理解し、䞊蚘の内容を考慮しお、プログラムの品質ずパフォヌマンスを改善できるこずを願っおいたす。



芪愛なる読者 JSコヌドを最適化するためのヒントを教えおください。



All Articles