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

JavaScriptエンジンから見るランタイム / 2024-04-25

Sho Miyamoto
April 25, 2024
1.5k

JavaScriptエンジンから見るランタイム / 2024-04-25

Sho Miyamoto

April 25, 2024
Tweet

Transcript

  1. - 近年、ランタイム(サーバサイド)が増えている - Cloudflare Workers (workerd), Bun, LLRT, WinterJS, …

    - これまでは採用されるエンジンV8が支配的だった(Node, Deno) - 実行パフォーマンス、セキュリティ、 API、... - 目的に照らして最適なエンジンを選択する時代に(?) - 起動時間、メモリフットプリント、ポータビリティ、カスタム性、 ... - エンジンの観点からランタイムを見てみる 👀 はじめに・まとめ
  2. - 話すこと - エンジンの割と基本的な話 - エンジンとは, どういう構造なのか, etc. - 各エンジン・ランタイムの特徴

    - 話さないこと - それぞれのトピックの詳細 - 処理系の最適化手法など - (発表時間が10mなので各話題 1 ページずつで軽く触れていきます) ⚠
  3. - エンジン - 言語処理系 - e.g. V8, … - ランタイム

    - ホスト環境、実行環境 - e.g. Chrome(blink), Node.js, … - 処理系を動かす環境 - JSと外界を仲介しているとも言える?( e.g. DOM, fs, net, …) - 例えばconsoleも非同期処理も持っていないエンジンがある エンジンとランタイム
  4. - 基本的にはランタイムがエンジンを埋め込む形 - e.g. V8 の C++ API をラップして Rust

    プログラムから呼ぶ - ランタイム兼エンジンもある( e.g. Hermes) - とはいえエンジン単体で動く CLI も大体ある(主にデバッグ・テスト用) - jsvu でまとめて簡単にインストールできる - ランタイム側でのカスタマイズ - host-definedな言語機能を実装する - e.g. イベントループ(非同期処理) - グローバルなAPIを生やす - e.g. Buffer, setTimeout, … エンジンとランタイムの関係
  5. この発表で触れるエンジン / ランタイム - V8 - JavaScriptCore - SpiderMonkey -

    QuickJS - Hermes - Wasmtime - (Engine262) - Node.js - Deno - workerd - Bun - WinterJS - LLRT - Hermes
  6. エンジンの構成 - JSを実行するための様々な機能を持つプログラム - インタープリタだったり JITコンパイラだったり - ブラウザに搭載されるエンジンは巨大 - JITコンパイラやインタプリタを複数持っている

    - 実行フェーズごとに使い分けている - ブラウザ非搭載の後発エンジンは比較的軽量なことが多い - 主にバイトコードインタプリタ( VM)のみの構成が多い - (そもそも新規でtest262をパスするエンジンを作るのは大変) - (V8レベルのエンジンを作るのはもっと大変、そもそも目的が違うのでモチベーションも少ない はず)
  7. パフォーマンス: JIT コンパイル - tl;dr: 実行中にバイトコードの一部をネイティブコードにコンパイルして部分的に置き換えて実行 する - 大変 -

    アーキテクチャごとに対応が必要 - コンパイルできるコードとできないコードがある - プロファイリング - 最適化すべき処理を決めるための情報(実行回数 , サイズ, メモリ, …) - 型や引数などの情報 - 脱最適化 - 想定と異なるコードに遭遇したら最適化前のコードに戻す - 段階 (Execution Tier) がある - Baseline JIT, Optimizing JIT, etc.
  8. パフォーマンス: Execution Tier - プロファイリング結果によって実行段階が上がっていく - Baseline - Optimizing -

    例 - V8 - Ignition (int.) -> SparkPlug (baseline) -> Maglev (opt.) -> TurboFan (opt.2) - JavaScriptCore - LLInt (interpreter) -> Baseline (baseline) -> DFG (opt.) -> FTL (opt.2) -
  9. 一般的なエンジンの構造 (JITがある場合) - 内部処理系 (Execution Tier) が複数ある - Baseline Interpreter

    -> Bytecode Interpreter -> JIT Compiler - それに伴い内部表現も複数ある - AST -> Bytecode -> IR (1 … n) -> Native Code - プログラムの実行フェーズの移行に伴い処理系・表現も移行
  10. - 多くのエンジンは1つのプロセスから複数の実行インスタンスを起動できる - ブラウザにおいては、例えばページ・ frame・workerごとにインスタンスが分かれている - ページごとにJS実行のためにエンジンのプロセスを立ち上げていたら遅い - 実行コンテキストを隔離する必要がある -

    もし仮に隔離されていなければ、悪意のあるサイトが iframe経由で親frameのコンテキストを盗 み見できたりコードを実行できたりする - * ブラウザのレンダリングプロセスの話も関わるので一概には言えないが ... - (そもそもES仕様に実行コンテキスト間の通信は規定されていない) - Realmのような同一実行コンテキスト内の話とは別 セキュリティ: Sandbox
  11. V8 - ブラウザ搭載 - 超高速 - 複数のExecution Tierを持つ - JITを持つ

    - (有名なので今更あまり書くことがない) - カスタムスナップショットAPI - (wasmtime + wizer のくだりで必要なので話したかったが、時間の関係上割愛) - Node.js も対応している (experimental)
  12. workerd - Cloudflare Workers で使われるランタイム - * 正確にはコアの部分はworkerdと共通だが違うコードベースとのこと - JS

    on Edge の先駆け・代表格 - サンドボックス環境 - エッジでは、独立した大量のアプリケーションを高速に実行する必要がある - 従来の(VM、)コンテナの代わりに V8 Isolate を利用している - ServiceWorker API - 通常のサーバサイドランタイムとは異なり、 ブラウザのServiceWorker APIに近いAPIを 実装している - req/resやCacheの操作など、エッジとSWで 共通する箇所が多く理にかなっている https://blog.cloudflare.com/introducing-cloudflare-workers/
  13. - 実行インスタンス - 1つのコンテキストを持つ - コンテキスト: 実行に必要なデータ - e.g. グローバル、ランタイムのカスタム

    API、スナップショット - 高速かつ軽量 - コンテナと比べて起動時間やメモリ使用量 が大幅に小さい - CF Workersの各アプリケーションは ブラウザでいうページ、 frame (内のJS部分)に当たる V8 Isolate https://developers.cloudflare.com/workers/reference/how-workers-works/
  14. SpiderMonkey - C++, Rust 製 - ブラウザ搭載 - MDNによるとBrendan Eich氏によって作られた世界初の

    JSエンジン - https://developer.mozilla.org/en-US/docs/Web/JavaScript/JavaScript_technologies_overview#javascript_implementations - Wasmにコンパイルできる - が、JITはあまり効かない - 代わりに Portable Baseline Interpreter が色々最適化
  15. WinterJS - WinterCG 準拠 - Wasm にコンパイルできる - Rust で書かれている

    & SpiderMonkey を利用している - 非同期バックエンドは Tokio - 開発元であるWasmerでは WinterJS をWasmにコンパイルしてから実行される 🤯
  16. QuickJS - C 製 - 軽量 - JIT なし・BytecodeInterpreterのみ -

    参照カウントベースのGC - 起動時間が高速・メモリ使用量が少ない - シンプルで使いやすいAPI - これからQuickJSを利用するエンジンが増えていくのでは - Wasmにコンパイルできる - QuickJS とアプリケーションコードをまとめて Wasm ランタイム上でJSを実行するプロジェクトもある (e.g. javy) - node:vmの代わりに Wasm にコンパイルした QuickJS を読み込んでアプリケーションで起動・実行する例 もある
  17. - Rust 製 - AWS が開発、主に Lambda での実行を想定 - 起動時間やメモリ使用量を高速化する狙い

    - デプロイされている既存コード (Node.js) との互換性もある - 起動が高速 - 一番の要因はJITを持たず軽量なQuickJSを採用したこと? - CPU-intensiveな処理や長時間動くアプリケーション以外では、 JIT が活きることは少なさそう - 🦭: サンドボックスといったセキュリティ面の担保はどうなってるのか気になる LLRT (Low Latency Runtime)
  18. - ReactNative 向けに作られたエンジン(すごい) - AOT コンパイラ + バイトコードインタプリタ - 最適化済みのバイトコードを事前に出力する

    - ブラウザとは異なり、RNは実行する対象コードが事前に与えられている - SSAまで落とし込んで解析・最適化する( V8の Maglev レベル?) - JITなし - 事前に最適化済みバイトコードがあるためそこまでモチベーションがなさそう - JITがあるとiOSで使えない(e.g. セキュリティ、消費電力) - experimentalで開発されていたが、現在は開発がストップしている(っぽい) - Hermes Static という AOT Compiler も開発中 - コンパイル対象はバイトコードではなくネイティブコード - その他のランタイムとは異なり実行マシンも決まっている Hermes
  19. Static Hermes - 絶賛開発中でまだ使われていないが、今後 Hermes に導入されるっぽい? - https://github.com/facebook/hermes/tree/static_h - エンジンではなくTS/Flowの静的コンパイラ

    - AssemblyScript に近い存在 - ネイティブコードを出力するので C/C++ 並のパフォーマンスとなる - コード生成は LLVM - shermes = LLVM フロントエンド - 🦭 個人的に昨年からずっと気になっているプロジェクト - 時間が足りないので詳しくは開発者の発表を見てください https://speakerdeck.com/tmikov2023/static-hermes-react-native-eu-2023-announcement
  20. - WebAssemblyランタイムの一つ - WASIをサポートしている - wasmtimeを利用してJSを実行するプラットフォームが存在する - e.g. Shopify function,

    Fastly Compute@Edge, … - JSを実行するといっても内部ではJSエンジンが実行されている - 1. JSエンジンをプログラムとして( Wasmにコンパイルして).wasmファイルに埋め込む - 2. JSソースコードをデータとして .wasmファイルに埋め込む - 3. JSソースコードを引数に JSエンジンを実行するWasmプログラムが完成する おまけ: wasmtime
  21. おまけ: wasmtime + wizer - JS on Wasm で使われている最適化 -

    JSエンジンのスナップショットと同じ方式
  22. - 近年、ランタイム(サーバサイド)が増えている - Cloudflare Workers, Bun, LLRT, WinterJS, … -

    これまでは採用されるエンジンV8が支配的だった(Node, Deno) - 実行パフォーマンス、セキュリティ、 API、... - 目的に照らして最適なエンジンを選択する時代に(?) - 起動時間、メモリフットプリント、ポータビリティ、カスタム性、 ... - エンジンの観点からランタイムを見てみる 👀 はじめに・まとめ