2 ポイント 投稿者 GN⁺ 2024-07-31 | 1件のコメント | WhatsAppで共有
  • Porfforは、JavaScriptを実行時ではなく事前にコンパイルして WebAssembly とネイティブバイナリ に変換する研究プロジェクト
  • インタープリタを同梱しない方式により、既存の JS→Wasm プロジェクトより出力物が 10〜30倍小さく高速 であることを目指す
  • ネイティブビルドでもランタイムをパッケージしないため、バイナリサイズが最大 1000倍 小さくなる可能性があり、例では約 90MB から 100KB 未満まで減少
  • JS で書かれており eval はなく、TypeScript を別ビルド段階なしで ネイティブサポート する構造を掲げる
  • AOT は静的解析の最適化と実行前コンパイルに有利だが、eval のような動的な JS 評価が難しく、まだ多くの JS が動作しない初期段階

Porfforが実現する実行方式

  • Porfforは、JavaScriptを WebAssemblyネイティブバイナリ に Ahead-of-Time コンパイルする研究プロジェクト
  • Porfforでコンパイルされた TypeScript バイナリ がこのページを提供している
  • 最初から AOT を念頭に置いて作られており、従来の JS 実行方式では難しかった最適化を試みる構造

WebAssembly とネイティブ出力の違い

  • JS → Wasm

    • Porfforの WebAssembly 出力は、既存の JS→Wasm プロジェクトより 10〜30倍小さく高速
    • 中核となる違いは、JS を直接コンパイルし、インタープリタをバンドルしない点にある
    • JS を Wasm 上で実行するとサンドボックス実行が可能になる一方で大きな性能損失が生じることがあり、Porffor はこのコストを減らすことに重点を置く
    • 適用可能な事例:
      • サーバーサイド JS ホスティング: エッジランタイムで Wasm サンドボックスにより、過剰な分離なしに安全な実行を提供できる
      • AOT の低いオーバーヘッドは、JIT と比べて同じハードウェア上で、最小限の性能低下でもっと多くの顧客を実行できる可能性を生む
      • リバースエンジニアリング耐性: 機密性の高い JS は、難読化よりもコンパイル済みコードのほうがリバースエンジニアリングしにくい場合がある
  • JS → Native

    • ランタイムをパッケージせずに JS を実際にコンパイルするため、バイナリサイズが最大 1000倍 小さくなる可能性がある
    • 例のサイズは約 90MB → 100KB 未満
    • 内部的には JS を C にコンパイルした後でネイティブにコンパイルするため、C を使える場所では JS を使える
    • 適用可能な事例:
      • 組み込み、ゲームコンソールなどでの 高速な JS 実行
      • 1MB 未満のワンクリック実行ファイルにコンパイルされる 小さな JS CLI アプリ

AOT がもたらす利点と制約

  • 従来のインタープリタや複数の JIT 段階は、起動時間と JS 性能のあいだでバランスを取る必要がある
  • AOT は先にコンパイルして後から実行するため、コンパイル速度は開発者体験には重要だが、ユーザー体験には影響しない
  • この方式は、C++ や Rust のような 静的解析ベースの最適化 を行う余地を与える
  • 主な欠点は、eval のような動的な JS 評価がなく、JS エンジンを新たに作る必要がある点
  • まだ初期段階のため多くの JS は動作しないが、改善作業が進められている
  • ECMAScript 互換性の進捗を追跡するため、コミットごとに公式テストスイートである Test262 を実行している

1件のコメント

 
GN⁺ 2024-07-31
Hacker News のコメント
  • Porffor の主開発者である Oliver が、Porffor にフルタイムで取り組むと発表: https://x.com/canadahonk/status/1818347311417938237

  • 似たようなものを考えたことはあるが、JavaScript で大幅に高い性能を出すのは難しいと思う。おそらく最善でも、JS を V8 の C++ 呼び出しへトランスパイルする程度だろう
    本当にすごい最適化は、TypeScript かそれに近いものをコンパイルするときに出てくる。型を活用すれば大きな利得が得られ、型がない部分は基本的に遅い JS 呼び出しに落ちることになる。インターフェースは仮想関数テーブルや直接呼び出しに縮約でき、マップの代わりに構造体上で動作させることもできる。IntFloat 型を用意し、必要なときに Number へ下げつつ、レジスタ内に置いておくこともできる
    核心的な問題は、TS と V8 がどちらも急速に変化する非標準の対象である点だ。こうしたプロジェクトには大きなチームが必要で、互換性の維持そのものが別の仕事になる

    • 追加の拡張なしでは、TypeScript は思ったほど役に立たない。そもそもその用途向けに設計されていないからだ
      簡単な例として、TypeScript は整数と浮動小数点数を区別せず、すべて数値として扱う。そのため、すべての配列アクセスで型変換が必要になる。静的コンパイルを助けるよう設計された TypeScript なら、おそらくこの区別があったはずだ
      より大きな問題は、TypeScript の 構造的部分型だ。この性質のため、コンパイラが関数に渡される非プリミティブ引数の物理的な構造を静的に決定することは事実上不可能になる。JIT は動的な形状解析ができるため、すべてのフィールドアクセスで JIT より悪い性能になる可能性がある
    • Porffor のコントリビューターとして同意しない。JavaScript にもコンパイル時点で改善できる余地はかなりある
      JS 向けの静的型解析ツールを作る取り組みは多くあり、非常に徹底した解析も可能だ。思い浮かぶ例としては少し古いが TAJS がある
    • このアイデアとある程度関連するプロジェクトとして AssemblyScript がある: https://www.assemblyscript.org
    • ECMAScript 4 は言語により良い型を追加しようとした試みだったが、ずいぶん前に残念ながら失敗した
      TypeScript でも integer のような型を指定できるとよい。通常の TS→JS コンパイルでは const val: intconst val: number とまったく同じに扱うとしても、TS を認識する最新のランタイムならその追加情報を活用できるはずだ
      const counter: Number のような構文が受け入れられるのか気になる
    • 「これを考えたことはあるが、より良い性能を得るのは難しい」と言ったあとで、サイトのトップ画面上部ですぐ説明されていることを求めるアプローチについて話している
      サイトが変わったのか、それとも自分が何か見落としているのか分からない
  • windmill.dev では、ユーザーがコードをデプロイする際に Bun build を使ってスクリプトとすべての依存関係を単一の JS ファイルにまとめ、それをロードしてコールドスタートとメモリ使用量を改善している。バンドルサイズのため、成果物は S3 に保存している
    すべてをネイティブにバンドルできるなら、状況はまったく変わる。Bun のコールドスタートがどれほど優れていても、小さなバイナリでそのままネイティブ実行するものには勝ちにくい

    • 開発者として同意する。Porffor が潜在的に役立てる興味深いユースケースに見える。いつか話してみたい
  • Wasm にアプローチする JS ランタイムが増えているのは良いことだと思う。このプロジェクトは、React Native プロジェクトの iOS・Android での速度を高めるための Facebook の JS エンジンである Static Hermes を思い起こさせる。
    どちらも JS test262 準拠を目標にしており、Porffor はネイティブ出力と Wasm 出力の両方をサポートする一方、Static Hermes は現在、主にネイティブ出力に注力している。Porffor は純粋な JS で書かれていて、自分自身をコンパイルできる方向を目指しており、Static Hermes は LLVM に依存している。Porffor は async/promise/await のサポートがまだ限定的で、Static Hermes は一部制限つきでサポートしている。Static Hermes は C++ で、Porffor は主に JS で書かれている。どちらも TypeScript をサポートするが、Static Hermes は TS AST を Flow にトランスパイルし、Porffor はネイティブにサポートしている。Static Hermes には eval のようなコンパイルが難しい JS の状況のための代替インタプリタがあり、Porffor は事前コンパイルのみをサポートする。
    全体として、このプロジェクトが勢いを得て エッジの JavaScript エンジンをさらに高速化できるか期待している。Wasmer の Syrus として投稿。
    https://github.com/facebook/hermes/discussions/1137
    https://github.com/tc39/test262
    https://wasmer.io

    • ちなみに Static Hermes は、JS を WASM にコンパイルする機能を完全にサポートしている。既存の LLVM バックエンドがあるので、ほぼ無料で手に入る機能だ。例は https://x.com/tmikov/status/1706138872412074204 を見ればよい。
      ただし私たちの焦点ではなく、主に React Native に集中している。その環境では WASM にはあまり意味がない。
      Static Hermes の最も重要な機能は、ランタイムの健全性を保証する型チェッカーだ。Porffor は非常に興味深く、しばらく見守ってきたし、うまくいってほしいと思っている。
    • Porffor のコントリビューターとして、良い比較だと思う。ただし Porffor も技術的には Promise をサポートしている。同期的に動作するだけだ。
      Kiesel と似たアプローチだ: https://kiesel.dev/
    • いくつか細かな訂正がある。Porffor はまだ完全には セルフホスティングされていないが、可能だと期待している。ただし Array.prototype.filterMath.sinatob のような組み込み機能は、部分的に自分自身をコンパイルしている。
      最近は Porffor も基本的な async/promise/await をサポートし始めた。まだそれほど良くはない。
    • LLVM に依存することを悪いことのように言ってしまった気がする。
  • JavaScript には簡単にコンパイルできるサブセットがあり、難しいのはその外側にある長いロングテールだ。それでも、その 境界がどこにあるのか、そしてそのサブセットでどれだけ得をできるのかの研究が進んでいるのは素晴らしい。

  • String.blink をサポートしている点が本当に気に入った。開発者に ユーモアと遊び心があるのは、いつでも良い兆候だ。

    • 「ECMAScript ホストが Web ブラウザのように動作」しようとしているなら、当然サポートすべきだ。仕様の一部だからだ: https://tc39.es/ecma262/multipage/additional-ecmascript-feat...
      実装も function() { return "" + this + ""; } 程度の些細なものなので、ECMAScript ホストが Web ブラウザでなくても実装する価値はある。その場合は任意だ。これが「ユーモアや遊び心」と関係しているとは期待していない。
    • String.blinktest262 に入っているので、プロジェクトの目標を達成するには実質的にサポートする必要がある。
  • 自分が見落としている微妙な違いが何なのか気になる。なぜ「事前コンパイル JS エンジン」が「JS-to-Wasm コンパイラ」より良い説明なのか分からない。主にフレーミング戦略なら、それはそれで構わない。

    • すでに JS インタプリタをバンドルして JS-to-WASM を行うプロジェクトがある。だから、そうした方式との違いをより明確にするための表現である可能性が高い。
  • ここで説明されている バージョン体系は少し疑わしい。
    ある変更によって一部の Test262 テストにリグレッションが生じると、バージョン番号も一緒に戻る可能性がある。つまり Porffor は、単調増加するバージョン番号と、必要な変更が Test262 のリグレッションを引き起こし得る能力を同時には持てない。
    https://github.com/CanadaHonk/porffor?tab=readme-ov-file#ver...

    • おそらく意図としては、Test262 のリグレッションを引き起こす作業は一時的に別ブランチで進め、リグレッションをなくすために必要な修正まで全て含まれた状態でのみ main にマージする、ということだろう。新しいバージョン番号は、そのマージが行われた後にだけ使えばよい。
  • ウェールズ語で「紫色」という意味だ。

    • 語源は紫色を意味するギリシャ語に由来し、同じ語根を持つ英単語としては、紫色の鉱物である porphyry がたぶん最も一般的だ。
  • さまざまな用途の JS エンジンがいくつも出てきているのを見るのは新鮮だ。
    アプリケーションにプラグインを組み込むため、llrt を通じて quickjs に Node 互換 API をさらに提供する作業をしてきた。
    https://github.com/awslabs/llrt