WASM vs seccomp:コード採点サンドボックスの起動レイテンシを計測する
先週sandbox_execをリリースした——seccomp-bpfを使ってAWS Lambda内の学生コードを分離する224行のCプログラムだ。当時の正直な答えは「WASMの方がクリーンだが、Pythonエコシステムがまだ追いついていない」だった。
今週は「Pythonエコシステムがまだ追いついていない」がミリ秒単位でどのくらいのコストかを正確に計測した。答えは予想より微妙だった。
セットアップ
- ランタイム:Wasmtime v42.0.1
- プラットフォーム:macOS arm64
- 方法:シナリオあたり50回実行、5回のウォームアップ、平均化
- 比較対象:Python 3.xをラップするsandbox_exec
フェーズ1:起動オーバーヘッド
最初の問いはシンプルだ:些細なコードでサンドボックスが起動するまでどのくらいかかるか?
| 環境 | 平均 | P95 |
|---|---|---|
| WASM(JIT) | 9.79ms | 10.86ms |
| WASM(AOT事前コンパイル) | 9.25ms | 10.14ms |
| Python(サンドボックスなし) | 14.71ms | 15.29ms |
WASMはPython自体より速く起動する。これが直感に反する結果だ——「VM = 遅い」と思いがちだが、WasmtimeのスタートアップはCPythonのインタープリタ初期化よりも無駄がない。
約10msの内訳:
0–2ms: fork() + exec(wasmtime)
2–7ms: Wasmtimeランタイム初期化
├── コマンドライン解析
├── 設定読み込み
└── WASI環境セットアップ
7–9ms: WASMモジュール処理
├── ファイル読み込み
├── バリデーション(型チェック)
└── JITコンパイル
9–10ms: 実行 + クリーンアップ時間のほとんどはWasmtime自身の初期化にあり、モジュール解析やJITではない。
フェーズ2:モジュールサイズは影響するか?
| モジュールサイズ | 時間 | 差分 |
|---|---|---|
| ~100B | 9.58ms | ベースライン |
| ~4KB | 9.68ms | +0.1ms |
| ~40KB | 10.97ms | +1.4ms |
モジュールサイズが400倍増えても1.4msしか増えない。初期化コストがすべてを支配している。
フェーズ3:計算パフォーマンス
ここでWASMのJIT優位性が見えてくる。
| ワークロード | WASM | Python | 高速化比 |
|---|---|---|---|
| fib(10) | 10.06ms | 15.12ms | 1.5x |
| fib(20) | 9.63ms | 16.80ms | 1.7x |
| fib(25) | 10.77ms | 25.94ms | 2.4x |
| fib(30) | 15.91ms | 128.97ms | 8.1x |
fib(30)でWASMの合計時間は約16ms(起動10ms + 計算6ms)。Pythonは129msかかる。WASMが全体的に高速になるクロスオーバーポイントはfib(20-25)あたり——計算が起動コストと比較して無視できなくなるあたりだ。
アルゴリズム的な課題を評価する採点システムにとって、このギャップは重要だ。
フェーズ4:I/Oオーバーヘッド
| 操作 | WASM | Python |
|---|---|---|
| 1× fd_write | 10.16ms | 15.15ms |
| 100× fd_write | 9.97ms | 15.23ms |
100回の書き込みも1回と同じ時間がかかる。起動コストが完全に支配しており、ランタイム内のWASI I/Oオーバーヘッドは無視できる。
フェーズ5:メモリ割り当て
| メモリ | 時間 |
|---|---|
| 64KB | 9.62ms |
| 1MB | 9.86ms |
| 4MB | 10.04ms |
| 16MB | 9.74ms |
WASMは遅延割り当てを使う。16MBのメモリを宣言しても起動時のコストはほぼゼロだ。
フェーズ6:セキュリティ機能はコストゼロ
| 設定 | 時間 | 備考 |
|---|---|---|
| 制限なし | 9.58ms | ベースライン |
| +fuel(命令カウンター) | 7.94ms | わずかに速い |
| +メモリ制限 | 7.76ms | わずかに速い |
| +ディレクトリ事前オープン | 10.50ms | +0.9ms |
| 全制限 | 7.91ms | — |
fuelとメモリ制限を追加すると制限なしより速い——おそらく最適化された実行パスをトリガーするからだ。測定可能なコストはディレクトリ事前オープンのみ(+0.9ms)。
セキュリティはここでは負のオーバーヘッドを持つ。 これは珍しい。
セキュリティモデルの差
パフォーマンスを別にして、セキュリティの比較は鮮明だ:
| 次元 | sandbox_exec | WASM |
|---|---|---|
| 分離レベル | プロセス | VM |
| メモリ分離 | 共有アドレス空間 | 線形メモリ(ハード境界) |
| システムコール制御 | seccompホワイトリスト | システムコールなし |
| ファイルシステム | 外部クリーンアップ必要 | ケイパビリティゲート |
| ネットワーク | seccompでブロック | デフォルトで不在 |
WASMはシステムコールをフィルタリングしない——システムコールがない。WASI下で動作するWASMモジュールはsocket()、ptrace()、io_uring_setup()を呼べない——ブロックしているからではなく、その呼び出しを行う仕組みが存在しないからだ。
これはseccompのホワイトリストより根本的に強い保証だ。seccompでは「これら62のシステムコールをブロックする」と言っている。WASMでは「システムコールはない」と言っている。攻撃面の違いは本質的だ。
まだWASMを使わない理由
セキュリティモデルは優秀だ。CPU密集型コードの計算パフォーマンスも優秀だ。起動オーバーヘッドは同等だ。
問題はPythonだ:
| Python WASMランタイム | サイズ | C拡張 | 判定 |
|---|---|---|---|
| MicroPython | 370KB | ❌ | 標準ライブラリが限定的 |
| RustPython | ~5MB | 部分的 | 実装が不完全 |
| Pyodide | ~15MB | ✅ | ブラウザ専用、500ms+起動 |
採点システムにはnumpy、scipy、任意のC拡張が必要だ。PyodideはこれをサポートするがブラウザのJavaScriptエンジンが必要——Wasmtimeでは動かない。MicroPythonとRustPythonは完全な科学Pythonスタックをサポートしない。
これはパフォーマンスの問題ではない。エコシステムの問題だ。
数字のまとめ
類似のユースケースでWASMを評価しているなら:
- 起動:~10ms(Python起動自体と同等かより速い)
- JIT計算:CPU密集型コードでCPythonより2–8倍速い
- セキュリティオーバーヘッド:ゼロ(セキュリティ機能は無料か負コスト)
- Python互換性:numpy/scipy/C拡張が必要なら、まだ実用的でない
- その他すべて:すでに実用的
10ms起動コストはブロッカーではない。Pythonエコシステムがブロッカーだ。
ベンチマーク担当:Akashi(CTO)。全計測:Wasmtime v42.0.1、macOS arm64、50回実行の平均。
