Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Deopt Explorer で Web アプリのパフォーマンスを改善しよう!

Deopt Explorer で Web アプリのパフォーマンスを改善しよう!

Webアプリケーションのパフォーマンス最適化には様々な観点や手法があります。
Deopt Explorer では、普段あまり気にされることのない V8(JavaScript ランタイム)の挙動に関心を置いたパフォーマンス改善の種を見つけることができます。
この資料では、Deopt Explorer を使って Web アプリケーションのパフォーマンスを改善するための事前知識および手法について紹介しています。

b4h0-c4t

July 18, 2023
Tweet

More Decks by b4h0-c4t

Other Decks in Programming

Transcript

  1. 自己紹介 - 名前 - BaHo - 所属 - フロントエンドエキスパートチーム -

    興味関心 - Webフロントエンド - ゲーム - JRPG とかアクション系が好き 2
  2. インラインキャッシングって何だよ 以前に実行したコードのキャッシュを利用して次回以降の実行速度を高速化する手法 5 const sum = (a, b) => {

    return a + b; }; sum(1, 2); // 3 sum(2, 3); // 5 JavaScript ランタイムでは、コードを実行する際に対象コードを実行可能なバイ ナリに変換してメモリへ展開し、それを実行します。 左の例では、sum(1, 2); を実行した際にメモリへ展開された sum() 関数を流用 して sum(2, 3) の処理が実行されるため、 sum(1, 2) と比較して sum(2, 3) の実 行は高速になります。 ※「implicit any だ!◦せ!」と思ったそこのあなた、これは JavaScriptです。
  3. インラインキャッシングができるとき・できないとき 実行バイナリ上で同様の計算処理が走るコードはできる 6 const sum = (a, b) => {

    return a + b; }; sum(1, 2); // 3 sum(2, 3); // 5 IC! const sum = (a, b) => { return a + b; }; sum(1, 2); // 3 sum(2, "3"); // "23" not IC! どちらも算術的な加算を実行しているのでイ ンラインキャッシングが有効 sum(1, 2) は算術的な加算だが sum(2, “3”) は文字列結合のため、 インラインキャッシングは無効 普段 TypeScript を利用しているのならプリミティブな値でこの現象を踏む人は少ないと思います
  4. Q. TS でインラインキャッシングできないことあるの? A. ありまぁす! 主にオブジェクトを取り扱うときに発生します。 Q. TSならオブジェクトにも型をつけられるよね? 実は JavaScript

    ランタイムは内部でオブジェクトの型を保持していて、TSのそれとはい くつかの不整合があります。 7 ※string | number とか Optional みたいな型定義でも発生するので実際は割とどこでも発生します
  5. Hidden Class の生成 プログラム内で関数が呼び出されると、呼び出された引数をもとに Hidden Classが生 成されます。 次回以降の呼び出しは、生成された Hidden Class

    をもとにインラインキャッシシングが 可能か検査が行われます。 9 const sum = (x) => { return x.a + x.b; }; sum({ a: 1, b: 2 }); // 3 sum({ a: 2, b: 3 }); // 5 map 0x38a5002079c9 extends Object { a: unknown; b: unknown; }
  6. Hidden Class の追加 引数オブジェクトの形が変わると、そのオブジェクトに対する Hidden Class の追加が行 われます。 10 const

    sum = (x) => { if (x.c) return x.a + x.b + x.c; return x.a + x.b; }; sum({ a: 1, b: 2 }); // 3 HiddlenClass1 sum({ a: 1, b: 2, c: 3 }); map HiddenClass1 extends Object { a: unknown; b: unknown; } map HiddenClass2 extends Object { a: unknown; b: unknown; c: unknown; // Added }
  7. Hidden Class のオフセット オブジェクトプロパティの定義順が変わっても新しい Hidden Class が生成されます。 11 const sum

    = (x) => { return x.a + x.b; }; sum({ a: 1, b: 2 }); // 3 HiddlenClass1 sum({ b: 1, a: 2 }); // 3 HiddenClass2 map HiddenClass1 extends Object { a: unknown; b: unknown; } map HiddenClass2 extends Object { b: unknown; a: unknown; }
  8. つまり... TSでの型定義に関わらず、Hidden Class が変わると (内部の型が変わるため) イン ラインキャッシングができない!となります ちなみに、 任意の引数オブジェクトに対して Hidden

    Class が複数存在している状態(性質)を Polymorphic、その数がより多いものを Megamorphic と呼びます。 12 ※ポリモーフィックです。どこかできいたことがありますね。
  9. 実際に Explore してみよう! - 必要なもの - お手持ちの PC - VS

    Code (Market Place から Deopt Explrer を追加しておこう) - Google Chrome (MS Edge でも良いらしいけど未検証 ) - 手順 - Chrome で検証したいWebアプリのトレースログを取得する - 取得した v8-log を Deopt Explorer で読み込む 18 chrome --no-sandbox --js-flags=--log-deopt,--log-i c,--log-maps,--log-maps-detail s,--log-internal-timer-events, --prof,... <トレースしたいURL> alias chrome="/path/to/Google\ Chrome"
  10. プログラムの解説 21 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" />

    <title>Document</title> </head> <body> <script src="./polymorphicExample.js" ></script> </body> </html> function f(x, y) { if (x <= y) { return { x, y }; } else { return { y, x }; } } function g(p) { const x = p.x; // polymorphic } for (let i = 0; i < 1000; i++) g(f(0, 1)); g(f(1, 0)); index.html polymorphicExample.js 関数 f() の引数が {x, y} か {y, x} として呼び出されているだけのページ
  11. 強調箇所をピークしてみる Uninitialized => Monomorphic x => Polymorphic x と Hidden

    Class が遷移している ことがわかります。 また、それぞれに対応した Map も参照されていますね。 23
  12. Map を覗く 1つ目(左)が {x, y} なのに対して、2つ目が {y, x} になっているのがわかります。 その

    Mapがどこで追加されたのかも Added by *** から追跡できます。 24
  13. プログラムを修正する 修正箇所がわかったので早速直しましょう 25 function f(x, y) { if (x <=

    y) { return { x, y }; } else { return { y, x }; } } function g(p) { const x = p.x; // polymorphic } for (let i = 0; i < 1000; i++) g(f(0, 1)); g(f(1, 0)); polymorphicExample.js function f(x, y) { return { x, y }; } function g(p) { const x = p.x; // polymorphic } for (let i = 0; i < 1000; i++) g(f(0, 1)); g(f(1, 0)); polymorphicExample.js ※身も蓋も無い修正