コンテンツにスキップ

エンジニアリング計算ツールに Rust と WebAssembly を選択した理由

ChainSolve の計算エンジンが Rust で記述され WebAssembly にコンパイルされている理由と、それがパフォーマンス、信頼性、ポータビリティにもたらす意味についての技術的考察。

BG ben godfrey · · 2 min read
RUST

要件

ChainSolve の計算エンジンを構築する際、当社は明確な要件セットを定めました。

  1. ネイティブに近い計算速度。 エンジニアリング計算には、大規模な行列、反復的なソルバー、および数百のノードを含む複雑な依存グラフが伴うことがあります。エンジンはこれらを秒単位ではなくミリ秒単位で評価する必要があります。

  2. ブラウザ実行。 ツールはブラウザで完全に機能し、計算のためのサーバー往復がないこと。機密プロジェクトに取り組むエンジニアは、所有権のある計算データを外部サーバーに送信することはできません。

  3. オフライン機能。 テスト施設、工場のフロア、顧客サイトにいるエンジニアは、多くの場合、インターネット接続が限定的またはまったくありません。ツールはロード後、オフラインで動作する必要があります。

  4. メモリ安全性。 メモリ破損により、クラッシュしたり、誤った結果を黙って生成したりする計算ツールは、無用の長物よりも悪いです。正確性は譲歩できません。

  5. ポータビリティ。 同じエンジンはブラウザで、CI/CD 統合用の Node.js で、および潜在的にはネイティブ CLI ツールとして実行する必要があります。

これらの要件により、選択肢はかなり絞られました。

JavaScript を選ばなかった理由

JavaScript はブラウザベースのアプリケーションの明らかな選択肢です。Web のネイティブ言語であり、優れたツール、および膨大なエコシステムを備えています。UI コードの場合、当社は JavaScript(具体的には React を備えた TypeScript)を使用しています。しかし、計算エンジンの場合、JavaScript には根本的な制限があります。

JavaScript は動的型付けされたガベージコレクション言語です。UI レンダリングおよびイベント処理の場合、これで問題ありません。数値計算の場合、2 つの問題が生じます。

パフォーマンスの上限。 V8 などの最新の JavaScript エンジンは動的言語としては非常に高速ですが、数値計算集約的な作業における事前コンパイル済みコードのパフォーマンスと一致することはできません。JIT コンパイルは予測不可能な一時停止を追加します。ガベージコレクションはレイテンシスパイクを追加します。これらは UI レイヤーでは許容されますが、一貫した高速な評価が必要な計算エンジンでは問題があります。

数値精度。 JavaScript には単一の数値型があります。IEEE 754 倍精度浮動小数点です。これはほとんどのエンジニアリング計算では十分ですが、JavaScript は浮動小数点の動作を制御する方法、ベクトル化計算用の SIMD イントリンシック、および正確な整数演算が必要な場合の整数型(依存グラフ上のグラフアルゴリズムなど)を提供していません。

JavaScript 計算エンジンは機能するでしょう。小〜中規模の計算では十分です。ただし、複雑なチェーンでパフォーマンスの上限に達し、数値エッジケースのために慎重な回避策が必要になります。

Rust を選んだ理由

Rust は計算要件のすべてを満たしています。

パフォーマンス。 Rust はガベージコレクターおよび実行時オーバーヘッドなしでマシンコード(または WebAssembly)にコンパイルされます。Rust の数値コードは C および C++ と同等のパフォーマンスを発揮します。ChainSolve の計算エンジンでは、GC 一時停止や JIT ウォームアップなしで、チェーン評価が一貫して高速です。

ガベージコレクションなしのメモリ安全性。 Rust の所有権システムはコンパイル時にメモリ安全性を保証します。ヌルポインタ逆参照、use-after-free バグ、データ競争はありません。正確性が最重要な計算ツールの場合、これは C および C++ に対する大きな利点です。

WebAssembly ターゲット。 Rust は wasm-pack および wasm-bindgen 経由で WebAssembly へのコンパイルについて第一級のサポートを備えています。結果の WASM モジュールはパフォーマンスを損なうことなく、あらゆる最新ブラウザでネイティブに近いスピードで実行されます。これにより、ブラウザ実行とオフライン機能が実現されます。

強力な型システム。 Rust の型システムにより、型システム自体にエンジニアリング制約をエンコードできます。UnitValue<Millimeters> を誤って UnitValue<Inches> に追加することはできず、コンパイラがそれを拒否します。これにより、エンジニアリングエラーの全カテゴリをコンパイル時に捕捉できます。

ポータビリティ。 同じ Rust コードはブラウザ実行用の WASM、CLI ツール用のネイティブバイナリ、および他のツールとの統合用の共有ライブラリにコンパイルされます。1 つのコードベース、複数のターゲット。

アーキテクチャ

ChainSolve のアーキテクチャは計算とプレゼンテーションを分離しています。

+------------------------------------------+
|  ブラウザ (TypeScript + React)           |
|  - UI コンポーネント                      |
|  - チェーンエディタ                      |
|  - ビジュアライゼーション                |
+------------------------------------------+
          |  wasm-bindgen インターフェース  |
+------------------------------------------+
|  WASM モジュール (Rust)                  |
|  - 依存グラフソルバー                    |
|  - ブロック評価エンジン                  |
|  - 単位変換システム                      |
|  - 式パーサー                            |
|  - 数値メソッドライブラリ                |
+------------------------------------------+

Rust/WASM モジュールは wasm-bindgen 経由で TypeScript レイヤーにクリーンな API を公開します。TypeScript レイヤーはすべての UI 関心事、レンダリング、ユーザーインタラクション、レイアウトを処理します。Rust レイヤーはすべての計算、グラフトラバーサル、ブロック評価、単位変換、式解析を処理します。

この分離は UI が計算時にブロックされないことを意味します。チェーン評価は WASM モジュールで発生し、結果を TypeScript に返し、UI が更新されます。非常に大規模なチェーンの場合、WASM モジュールを Web Worker で実行して、UI スレッドを完全にレスポンシブに保ちます。

パフォーマンス結果

開発ビルドからの代表的なベンチマーク(mid-range ノートパソコン、M2 MacBook Air で測定):

操作JavaScript (ベースライン)Rust/WASM
100 ブロックチェーンの評価12 ms0.8 ms
1,000 ブロックチェーンの評価340 ms8 ms
トポロジカルソート (1,000 ノード)5 ms0.3 ms
単位変換 (10,000 値)18 ms0.6 ms
式解析 + 評価0.4 ms0.02 ms

Rust/WASM の実装は同等の JavaScript よりも一貫して 15~40 倍高速です。小規模なチェーンの場合、両方とも十分に高速です。大規模なチェーン(本番ユーザーは必ず作成する)の場合、違いは即座のフィードバックと知覚可能な遅延の間です。

課題とトレードオフ

Rust と WebAssembly は抜きん出た選択肢ではありません。

学習曲線。 Rust は JavaScript または Python よりも複雑な言語です。所有権システムは強力ですが、開発に時間がかかる思考モデルが必要です。単独の創業者の場合、これは短期的な速度の犠牲による長期的な生産性への投資です。

WASM バイナリサイズ。 コンパイル済み WASM モジュールは現在約 800 KB gzip です。これは最初のページロードに対する有意な追加です。当社はこれを遅延ロードで軽減し、WASM モジュールはUI シェルがレンダリングされた後、非同期でロードされます。

デバッグ。 WASM にコンパイルされた Rust コードのデバッグは改善されていますが、ブラウザ DevTools で JavaScript をデバッグするほど便利ではありません。当社は Rust のテストスイート(ネイティブ実行)に大きく依存し、ブラウザデバッグに WASM 固有のログを使用しています。

エコシステム。 数値計算用の Rust エコシステムは成長していますが、Python の NumPy/SciPy エコシステムほど成熟していません。既存の Rust クレートが不十分だった場合、一部の数値メソッドをスクラッチから実装しています。

これらのトレードオフにもかかわらず、Rust と WebAssembly の使用決定は、結果として得られるエンジンのパフォーマンスと信頼性の特性によって検証されています。エンジニアが安全に重要な決定のための結果に依存するツールの場合、Rust が提供するコンパイル時の保証は追加の開発の複雑さに値します。

結論

ChainSolve の計算エンジンに Rust と WebAssembly を選択することは、テクノロジーファッションではなく、エンジニアリング要件によって駆動された意図的なアーキテクチャ決定でした。ブラウザでのネイティブに近いパフォーマンス、ガベージコレクションなしのメモリ安全性、およびコンパイル時の単位エラーをキャッチする強力な型システムは、エンジニアリング計算ツールの nice-to-have ではありません。これらは要件です。

Rust/WASM アーキテクチャの技術的詳細に関心がある場合、または自分自身のエンジニアリングツールについて同様のテクノロジー選択を評価している場合、当社は会話を歓迎します。当社のお問い合わせページ経由でお気軽にお問い合わせください。

Written by
BG
ben godfrey
Godfrey Engineering Ltd. のエンジニア