新しいV8およびNode.jsの速床今日ず明日の最適化手法

Node.jsは、圓初からV8 JS゚ンゞンに䟝存しおおり、V8 JS゚ンゞンは誰もが知っおいる、愛する蚀語コマンドの実行を提䟛したす。 V8は、GoogleがChromeブラりザヌ甚に䜜成したJavaScript仮想マシンです。 圓初から、少なくずも競合゚ンゞンよりも高速なJavaScriptを実珟するためにV8が䜜成されたした。 匷い型付けのない動的蚀語の堎合、高いパフォヌマンスを実珟するのは簡単なこずではありたせん。 V8およびその他の゚ンゞンは、この問題をより良く、より良く解決しおいたす。 ただし、新しい゚ンゞンは「JS実行の速床を䞊げる」だけではありたせん。 これは、コヌド最適化ぞの新しいアプロヌチの必芁性でもありたす。 今日最速だったすべおが将来最倧のパフォヌマンスで私たちを喜ばせるわけではありたせん。 遅いずみなされたすべおがそうであるずは限りたせん。

TurboFan V8の仕様はコヌドの最適化にどのように圱響したすか 近い将来に自分自身を瀺すために、今日の技術はどのように最適ず考えられおいたすか 最近のV8パフォヌマンスキラヌはどのように動䜜したすかたた、それらに䜕を期埅できたすか この資料では、これらおよび他の倚くの質問に察する答えを芋぀けようずしたした。

デビッド・マヌク・クレメンツずマッテオ・コリヌナの共同䜜業の成果がここにありたす。 玠材は、V8開発チヌムのFrancis HinkelmannずBenedict Meirerによっお確認されたした。



JavaScriptを高速で実行できるV8゚ンゞンの䞭心郚分は、JITJust In Timeコンパむラヌです。 実行時にコヌドを最適化できる動的コンパむラヌです。 V8が最初に䜜成されたずき、JITコンパむラヌはFullCodeGenず名付けられたした。これは Yang Guoが正しく述べおいるようにこのプラットフォヌムの最初の最適化コンパむラヌです。 次に、V8チヌムはCrankshaftコンパむラを䜜成したした。これには、FullCodeGenには実装されおいない倚くのパフォヌマンス最適化が含たれおいたした。

90幎代からJavaScriptを芋お、ずっず䜿っおいた人ずしお、どの゚ンゞンに関係なく、JSコヌドのどのセクションが動䜜が遅く、すぐに完党に非自明になるこずがしばしばあるこずに気付きたした。䜿甚されたす。 プログラムが予想よりも遅く実行された理由は、しばしば理解するのが困難でした。

近幎、Matteo Collinaず私は、Node.jsの高性胜コヌドを蚘述する方法を芋぀けるこずに焊点を圓おおきたした。 圓然、これは、V8 JS゚ンゞンによっおコヌドが実行されるずきに、どのアプロヌチが高速で、どのアプロヌチが遅いかを知るこずを意味したす。

V8チヌムが新しいJITコンパむラヌTurboFanを䜜成したので、今床はパフォヌマンスに関するすべおの前提条件を確認したす。

最適化コンパむルの攟棄に぀ながる、よく知られた゜フトりェア構成を怜蚎したす。 さらに、ここでは、さたざたなバヌゞョンのV8のパフォヌマンスを研究するこずを目的ずした、より耇雑な研究​​を扱いたす。 これはすべお、異なるバヌゞョンのNodeおよびV8を䜿甚しお起動される䞀連のマむクロベンチマヌクを通じお行われたす。

もちろん、V8甚にコヌドを最適化する前に、最初にAPI、アルゎリズム、およびデヌタ構造の蚭蚈に焊点を合わせる必芁がありたす。 これらのマむクロベンチマヌクは、NodeでのJavaScriptのパフォヌマンスがどのように倉化しおいるかを瀺す指暙ず芋なすこずができたす。 これらのむンゞケヌタヌを䜿甚しお、コヌドの党䜓的なスタむルず、通垞の最適化を適甚した埌にパフォヌマンスを改善する方法を倉曎できたす。

バヌゞョンV8 5.1、5.8、5.9、6.0、および6.1のマむクロベンチマヌクのパフォヌマンスを怜蚎したす。

V8バヌゞョンずNodeバヌゞョンの関係を明確にするために、次の点に泚意しおください。V85.1゚ンゞンはNode 6で䜿甚され、Crankshaft JITコンパむラはここで䜿甚され、V8 5.8゚ンゞンはNodeバヌゞョン8.0〜8.2で䜿甚され、Crankshaftはここで䜿甚されたす。タヌボファン。

珟圚、ノヌド8.3、たたはおそらく8.4には、V8゚ンゞンバヌゞョン5.9たたは6.0が存圚するこずが予想されたす。 この蚘事の執筆時点でのV8の最新バヌゞョンは6.1です。 node-v8実隓リポゞトリのNodeに統合されおいたす。 蚀い換えれば、V8 6.1はNodeの将来のバヌゞョンで䜿甚されるこずになりたす。

この蚘事の準備で䜿甚されたテストコヌドずその他の資料は、 ここにありたす。
これは、特に未凊理のテスト結果があるドキュメントです。

ほずんどのマむクロベンチマヌクは、Macbook Pro 2016、3.3 GHz Intel Core i7、16 GB 2133 MHz LPDDR3メモリで実行されたす。 それらの䞀郚数倀の操䜜、オブゞェクトのプロパティの削陀は、MacBook Pro 2014、3 GHz Intel Core i7、16 GB 1600 MHz DDR3メモリで実行されたした。 Node.jsの異なるバヌゞョンのパフォヌマンス枬定は、同じコンピュヌタヌで実行されたした。 他のプログラムがテスト結果に圱響しないこずを確認したした。

テストを芋お、結果が将来のノヌドにずっお䜕を意味するかに぀いお話したしょう。 すべおのテストは、 benchmark.jsパッケヌゞを䜿甚しお実行されたした。各図​​のデヌタは、1秒あたりの操䜜数を瀺しおいたす。぀たり、倀が倧きいほど優れおいたす。

問題を詊す/キャッチする


よく知られおいる最適化解陀パタヌンの1぀は、 try/catchブロックを䜿甚するこずです。

以䞋、テストの説明のリストの括匧内に、英語の短いテスト名が蚘茉されおいるこずに泚意しおください。 これらの名前は、チャヌトで結果を瀺すために䜿甚されたす。 さらに、テスト䞭に䜿甚されたコヌドをナビゲヌトするのに圹立ちたす。
このテストでは、4぀のテストケヌスを比范したす。


→ GitHubでコヌドをテストする



try/catchパフォヌマンスぞの悪圱響に぀いお既に知られおいるこずがNode 6V8 5.1で確認されおおり、Node 8.0-8.2V8 5.8 try/catchパフォヌマンスぞの圱響ははるかに小さいこずがわかりたす。

たた、 tryブロックから関数を呌び出すこずは、 try倖郚で呌び出すよりもはるかに遅いこずに泚意する必芁がありたす。これは、ノヌド6V8 5.1およびノヌ​​ド8.0-8.2V8 5.8の䞡方に圓おはたりたす。

ただし、ノヌド8.3+では、 tryブロックから関数を呌び出しおもパフォヌマンスにはほずんど圱響したせん。

それにもかかわらず、萜ち着かないでください。 最適化セミナヌのいく぀かの資料に取り組んでいる間に、かなり特定の状況がTurboFanでの最適化解陀/再最適化の無限のサむクルに぀ながる可胜性がある堎合に゚ラヌを発芋したした。 これは、次のパフォヌマンスキラヌパタヌンず考えられたす。

オブゞェクトからプロパティを削陀する


長幎にわたり、JSで高性胜コヌドを蚘述したい人はだれでも少なくずも、プログラムの最も負荷の高い郚分に最適なコヌドを蚘述する必芁がある堎合、 deleteコマンドは回避されおいたした。

deleteの問題は、V8がJavaScriptオブゞェクトの動的な性質を凊理する方法ず、䜎レベルの゚ンゞン実装でのプロパティの怜玢を耇雑にするプロトタむプチェヌン朜圚的に動的に起因したす。

プロパティを持぀高性胜オブゞェクトを䜜成するV8゚ンゞンのアプロヌチは、オブゞェクトの「フォヌム」、぀たりオブゞェクトが持぀キヌず倀プロトタむプチェヌンのキヌず倀を含むに基づいお、C ++レベルでクラスを䜜成するこずです。 これらの構造は、「隠しクラス」ずしお知られおいたす。 ただし、このタむプの最適化はプログラムの実行䞭に実行されたす。 オブゞェクトの圢状が䞍明な堎合、V8には別のプロパティ怜玢モヌドがありたすハッシュテヌブル怜玢。 このようなプロパティ怜玢は非垞に遅くなりたす。

埓来、 deleteコマンドを䜿甚しおオブゞェクトからキヌを削陀する堎合、プロパティにアクセスする埌続の操䜜は、ハッシュテヌブルを怜玢しお実行されたす。 そのため、プログラマヌはdeleteコマンドを䜿甚せず、代わりにプロパティをundefinedに蚭定したす。倀を砎壊するずいう点では、同じ結果になりたすが、プロパティの存圚を確認する際に耇雑さが増したす。 ただし、通垞、このアプロヌチは、たずえばJSON.stringify出力にundefined倀が含たれundefinedためJSON仕様によるずundefinedは有効な倀に適甚されないため、シリアル化のためにオブゞェクトを準備するずきに十分です。

ここで、新しいTurboFan実装がオブゞェクトからプロパティを削陀する問題を解決するかどうかを調べたしょう。

ここでは、3぀のテストケヌスを比范したす。


→ GitHubでコヌドをテストする



V8 6.0および6.1ノヌドリリヌスではただ䜿甚されおいたせんでは、オブゞェクトに最埌に远加されたプロパティを削陀するず、プログラム実行の最適化されたTurboFanパスに察応するため、プロパティをundefined蚭定するよりも高速です。 これは、V8開発チヌムがdeleteコマンドのパフォヌマンスの改善に取り組んでいるこずを瀺しおいるため、非垞に優れおいたす。

ただし、この挔算子を䜿甚するず、远加されたプロパティの最埌ではないプロパティがオブゞェクトから削陀された堎合、プロパティにアクセスするずきに深刻なパフォヌマンスの䜎䞋に぀ながりたす。 この芳察はJacob Kummerovによっお助けられたした。JacobKummerovは、最埌に远加されたプロパティを削陀するオプションのみが調査されたテストの特異性を指摘したした。 圌に感謝したす。 その結果、 deleteコマンドは将来のNodeリリヌス甚に蚘述されたコヌドでどれだけ䜿甚でき、たた䜿甚すべきだず蚀っおも、これを行わないこずをお勧めしたす。 deleteコマンドは匕き続きパフォヌマンスに悪圱響を及がしたす。

リヌクず匕数の配列ぞの倉換


通垞の関数で䜿甚できる暗黙的に生成されたargumentsオブゞェクトの兞型的な問題 argumentsオブゞェクトの矢印関数は䜿甚できたせんは、配列ではなく配列のように芋えるこずです。

配列のメ゜ッドたたはその動䜜の機胜を䜿甚するには、 argumentsのむンデックス付きプロパティを配列にコピヌする必芁がありたす。 過去に、JS開発者はより短いコヌドずより速いコヌドを同䞀芖する傟向がありたした。 このアプロヌチは、クラむアントコヌドの堎合、ブラりザヌがダりンロヌドするデヌタの量を削枛するこずを可胜にしたすが、サヌバヌコヌドで問題を匕き起こす可胜性がありたす。サヌバヌコヌドでは、プログラムのサむズが実行速床よりもはるかに重芁ではありたせん。 その結果、 argumentsオブゞェクトを配列に倉換する非垞に短い方法が非垞に䞀般的になりたした

Array.prototype.slice.call(arguments) 。 このようなコマンドは、 Arrayオブゞェクトのsliceメ゜ッドを呌び出し、 argumentsオブゞェクトをこのメ゜ッドのthisコンテキストずしお枡したす。 sliceメ゜ッドは、配列のように芋えるオブゞェクトを怜出し、その埌にゞョブを実行したす。 その結果、 argumentsオブゞェクトのコンテンツから配列に組み立おられた配列を取埗したす。

ただし、暗黙的に生成されたargumentsオブゞェクトが関数のコンテキスト倖に枡される堎合たずえば、 Array.prototype.slice.call(arguments)呌び出すずきなど、関数から返されるか別の関数に枡される堎合、これは通垞パフォヌマンスArray.prototype.slice.call(arguments)匕き起こしたす。 この声明を調べたす。

次のマむクロベンチマヌクは、V8の4぀のバヌゞョンで盞互に関連する2぀の状況を調査するこずを目的ずしおいたす。 ぀たり、これはargumentsリヌクのコストず配列にargumentsをコピヌするコストであり、 argumentsオブゞェクトの代わりに関数の倖郚に枡されたす。

テストケヌスは次のずおりです。


→ GitHubでコヌドをテストする



次に、パフォヌマンス特性の倉化を匷調するために、折れ線グラフの圢匏で衚瀺される同じデヌタを芋おみたしょう。



これらすべおから導き出せる結論を以䞋に瀺したす。 関数の入力デヌタを配列の圢で凊理する生産的なコヌドを曞く必芁がある堎合経隓から私は非垞に頻繁に必芁ずするこずを知っおいたす、ノヌド8.3以降では拡匵挔算子を䜿甚する必芁がありたす。 Node 8.2以䞋では、 forルヌプを䜿甚しお、 argumentsからキヌを新しい以前に䜜成された配列にコピヌする必芁がありたす詳现に぀いおは、テストコヌドを参照。

さらに、ノヌド8.3+では、 argumentsオブゞェクトを他の関数に枡すずパフォヌマンスが䜎䞋するため、完党な配列を必芁ずせず、配列のように芋えるが配列ではない構造を操䜜できる堎合、他のパフォヌマンス䞊の利点がありたす。

郚分䜿甚カリヌ化および関数コンテキストバむンディング


関数を郚分的に適甚たたはカリヌ化するず、囲たれた回路の可芖領域に特定の状態を保存できたす。

䟋

 function add (a, b) { return a + b } const add10 = function (n) { return add(10, n) } console.log(add10(20)) 

この䟋では、 add関数のパラメヌタヌは、 add10関数の数倀10ずしお郚分的に適甚されたす。

bindメ゜ッドのおかげで、EcmaScript 5以降、関数のより短い圢匏の郚分的な䜿甚が可胜になりたした。

 function add (a, b) { return a + b } const add10 = add.bind(null, 10) console.log(add10(20)) 

ただし、通垞、 bindメ゜ッドは䞊蚘のクロヌゞャヌメ゜ッドよりも倧幅に遅いため、䜿甚されたせん。

このテストでは、V8の異なるバヌゞョンでbindずスナップの䜿甚の違いを枬定したす。 比范のために、ここでは元の関数の盎接呌び出しが䜿甚されおいたす。

以䞋に4぀のテストケヌスを瀺したす。


→ GitHubでコヌドをテストする



テスト結果の線圢図は、最新バヌゞョンのV8で機胜を操䜜するための考慮された方法の間にほずんど完党な違いがないこずを明確に瀺しおいたす。 興味深いこずに、矢印関数を䜿甚する郚分的なアプリケヌションは、通垞の関数を䜿甚するよりもはるかに高速です少なくずもテストでは。 実際、盎接関数呌び出しずほが䞀臎したす。 V8 5.1ノヌド6および5.8ノヌド8.0-8.2では、 bind非垞に遅く、これらの目的で矢印関数を䜿甚するず最高速床を達成できるこずが明らかです。 ただし、V8バヌゞョン5.9ノヌド8.3+以降、 bindパフォヌマンスは倧幅に向䞊しおいたす。 このアプロヌチは、V8 6.1将来のバヌゞョンのノヌドで最速ですただし、ここでのパフォヌマンスの違いはほずんど区別できたせん。

Nodeのすべおのバヌゞョンで最速のカレヌ方法は、矢印関数を䜿甚するこずです。 最近のバヌゞョンでは、この方法ずbindの䜿甚の違いbind重芁でbindたせん。珟圚の状態では、通垞の関数を䜿甚するよりも高速です。 ただし、より完党な党䜓像を埗るには、さたざたなサむズのデヌタ​​構造を持぀関数のより倚くのタむプの郚分適甚を調査する必芁があるため、埗られた結果がどのような状況でも有効であるずは蚀えたせん。

機胜コヌドサむズ


眲名、スペヌス、さらにはコメントを含む関数のサむズは、V8が関数をむンラむン化できるかどうかに圱響を䞎える可胜性がありたす。 はい。関数にコメントを远加するず、パフォヌマンスが玄10䜎䞋する可胜性がありたす。 これは将来倉曎されたすか

このテストでは、3぀のシナリオを怜蚎したす。


→ GitHubでコヌドをテストする



V8 5.1ノヌド6では、small関数ずlongの合蚈テストの合蚈は同じ結果を瀺したす。 これは、埋め蟌みの仕組みを完党に瀺しおいたす。 小さな関数を呌び出すず、V8がこの関数の内容を呌び出される堎所に曞き蟌むこずに䌌おいたす。 したがっお、関数のテキストを蚘述するずきコメントを远加しおも、呌び出しの堎所に手動で埋め蟌みたすが、パフォヌマンスは同じです。 繰り返しになりたすが、V8 5.1ノヌド6では、関数が特定のサむズに達した埌、コメントが远加された関数を呌び出すず、コヌドの実行が倧幅に遅くなるこずがわかりたす。

Node 8.0-8.2V8 5.8では、小さな関数を呌び出すコストが著しく増加したこずを陀いお、党䜓ずしお状況は同じたたです。 これはおそらく、CrankshaftずTurboFanの芁玠の混合によるものです。1぀の関数がCrankshaftにあり、もう1぀の関数がTurboFanにある堎合、埋め蟌みのメカニズムの故障に぀ながりたす぀たり、連続しお組み蟌たれた関数のクラスタヌ間の移行が発生するはずです。

V8 5.9以降ノヌド8.3以降では、スペヌスやコメントなどの無関係な文字を远加しおも、関数のパフォヌマンスには圱響したせん。 これは、Curboshaftなどの文字をカりントする代わりに、TurboFanが抜象構文ツリヌ AST、 抜象構文ツリヌ を䜿甚しお関数のサむズを蚈算するためです。 TurboFanは、関数のバむト数を考慮する代わりに、関数の実際の呜什を分析したす。そのため、V8 5.9ノヌド8.3+ スペヌスから始たり、倉数名を構成する文字、関数シグネチャ、およびコメントは、関数が埋め蟌たれる 。 さらに、関数の党䜓的なパフォヌマンスが䜎䞋しおいるこずに泚意する必芁がありたす。

ここでの䞻な結論は、機胜はできる限り小さくする䟡倀があるずいうこずです。 珟時点では、関数内の䞍芁なコメントさらにはスペヌスを避ける必芁がありたす。 さらに、最高のパフォヌマンスを目指しおいる堎合、関数を手動で埋め蟌む぀たり、関数コヌドを呌び出しの堎所に転送し、関数を呌び出す必芁がなくなるこずは、最速のアプロヌチであり続けたす。 もちろん、ここでバランスを取る必芁がありたす。実際の実行可胜コヌドが特定のサむズに達するず、関数はずにかく組み蟌たれないため、他の関数のコヌドを思いがけずコピヌするず、パフォヌマンスの問題が発生する可胜性があるためです。 蚀い換えるず、手動で関数を埋め蟌むこずは、脚を撃぀可胜性がありたす。 ほずんどの堎合、コンパむラに関数の埋め蟌みを委任するこずをお勧めしたす。

32ビットおよび64ビット敎数


JavaScriptの数倀型はNumberのみであるこずがよく知られおいたす。

ただし、V8はC ++で実装されおいるため、JavaScriptの数倀の基本的なタむプは遞択の問題です。

敎数の堎合぀たり、小数点なしでJSで数倀を指定する堎合、V8はすべおの数倀が32ビットであるず芋なしたす-それらがそうでなくなる限り。 倚くの堎合、数倀は2147483648〜2147483647の範囲にあるため、これは公平な遞択のようです。 JS番号党䜓が2147483647を超える堎合、JITコンパむラヌは、数倀の基本型を倍粟床型浮動小数点に動的に倉曎する必芁がありたす-これは、朜圚的に、他の最適化に特定の圱響を䞎える可胜性がありたす。

このテストでは、3぀のシナリオを怜蚎したす。


→ GitHubでコヌドをテストする



この図から、ノヌド6V8 5.1、ノヌド8V8 5.8、たたはノヌドの将来のバヌゞョンに぀いおも、䞊蚘の芳察結果は有効であるず蚀えたす。 ぀たり、2147483647より倧きい敎数を䜿甚した蚈算は、関数が最倧倀の半分たたは3分の2の領域にある速床で実行されるずいう事実に぀ながりたす。 したがっお、デゞタルIDが長い堎合は、文字列に入れおください。

さらに、ノヌド6V8 5.1およびノヌ​​ド8.1および8.2V8 5.8では、ノヌド8.3+V8 5.9+よりも32ビットの範囲内の数倀での操䜜がはるかに高速に実行されるこずが非垞に顕著です。  ただし、Node 8.3+V8 5.9+の倍粟床数の操䜜は高速です。 これはおそらく、32ビット数の凊理速床が遅いためであり、テストコヌドで䜿甚される関数たたはforルヌプの呌び出し速床には適甚されたせん。

Jakob Kummerov 、 Yang Guo 、およびV8チヌムは、このテストの結果をより正確に、より正確にするのに圹立ちたした。 これに぀いお圌らに感謝しおいたす。

オブゞェクトプロパティの列挙


オブゞェクトのすべおのプロパティの倀を取埗し、それらのアクションを実行するこずは䞀般的なタスクです。 それを解決する方法はたくさんありたす。 V8ずNodeの調査枈みのバヌゞョンの䞭で、どのメ゜ッドが最速かを調べたす。

V8のすべおのテスト枈みバヌゞョンが受けた4぀のテストは次のずおりです。


さらに、V8バヌゞョン5.8、5.9、6.0、および6.1の3぀の远加テストを実斜したした。


このバヌゞョンはEcmaScript 2017 Object.values組み蟌みメ゜ッドをサポヌトしおいないため、V8 5.1ノヌド6ではこれらのテストを実行したせんでした。

→ GitHubでコヌドをテストする



Node 6V8 5.1およびNode 8.0-8.2V8 5.8では、 for-inルヌプを䜿甚するこずが、間違いなくオブゞェクトのキヌを反埩凊理し、そのプロパティ倀にアクセスする最速の方法です。 40 , 5 , , Object.keys , 8 .

V8 6.0 (Node 8.3) for-in - , . , .

V8 6.1 ( , Node), , Object.keys , , for-in , , , for-in V8 5.1 5.8 (Node 6, Node 8.0-8.2).

, TurboFan — , . , , .

Object.values , Object.keys . , , . , , .

, , for-in - , . , .


JS — , , .

:


→ GitHub



Node 6 (V8 5.1) .

Node 8.0-8.2 (V8 5.8), EcmaScript 2015, , -. , , Node.

V8 5.9 .

, V8 6.0 (, Node 8.3 8.4) 6.1 ( V8 Node), . 500 ! .


, . , , . , , , ( ).

, , TurboFan . .


(, ), , . . , . , , , , - . , , , . .

:


→ GitHub



, V8.

V8 6.1 ( , Node) , . , , node-v8, « » V8, V8 6.1.

, , , , , . , , , , API .

, V8 , , , d8 . , Node. , , Node ( , Node V8). . , .

debugger


, , debugger .

-. .

:


→ GitHub



. V8 debugger .

, without debugger V8.

:


, , V8 . Node.js, , Pino .

, 10 ( — ) Node.js 6.11 (Crankshaft).


— , V8 6.1 (TurboFan).


, , Winston JIT- TurboFan. , , , , . Crankshaft TurboFan, , Crankshaft, TurboFan . Winston, , , , Crankshaft, TurboFan. , Pino Crankshaft. .

たずめ


, , V8 5.1, 5.8 5.9, TurboFan V8 6.0 6.1. , , , , , .

TurboFan (V8 6.0 ). TurboFan , , , « V8» . (Chrome) (Node) . , , , . , . , TurboFan (, Winston Pino).

- JavaScript, , , , - , - . JS-, , V8, .

芪愛なる読者 JavaScript ?

Source: https://habr.com/ru/post/J334806/


All Articles