
ここでは、WebAssemblyモジュールを作成する方法、それらを操作する方法、JSで書かれたモジュールであるかのようにブラウザのクライアントコードから呼び出す方法について説明します。 フィボナッチ数検索アルゴリズムの実装の2つのセットを検討します 。 それらの1つは通常のJavaScript関数で表され、2つ目はCで記述され、WebAssemblyモジュールに変換されます。 これにより、同様の問題を解決する際のwasmとJSのパフォーマンスを比較できます。
テストコード
フィボナッチ数を見つけるための3つのアプローチを検討します。 最初はループを使用します。 2番目は再帰を伴います。 3番目は、メモ化の手法に基づいています。 それらはすべてJavaScriptとCで実装されています。
JSコードは次のとおりです。
function fiboJs(num){ var a = 1, b = 0, temp; while (num >= 0){ temp = a; a = a + b; b = temp; num--; } return b; } const fiboJsRec = (num) => { if (num <= 1) return 1; return fiboJsRec(num - 1) + fiboJsRec(num - 2); } const fiboJsMemo = (num, memo) => { memo = memo || {}; if (memo[num]) return memo[num]; if (num <= 1) return 1; return memo[num] = fiboJsMemo(num - 1, memo) + fiboJsMemo(num - 2, memo); } module.exports = {fiboJs, fiboJsRec, fiboJsMemo};
Cで書かれたものと同じものを次に示します。
int fibonacci(int n) { int a = 1; int b = 1; while (n-- > 1) { int t = a; a = b; b += t; } return b; } int fibonacciRec(int num) { if (num <= 1) return 1; return fibonacciRec(num - 1) + fibonacciRec(num - 2); } int memo[10000]; int fibonacciMemo(int n) { if (memo[n] != -1) return memo[n]; if (n == 1 || n == 2) { return 1; } else { return memo[n] = fibonacciMemo(n - 1) + fibonacciMemo(n - 2); } }
ここでは実装の複雑さについては説明しませんが、主な目標は異なります。 必要に応じて、 ここでフィボナッチ数について読むことができます。 ここでは、これらの数を見つけるための再帰的アプローチに関する興味深い議論を行います。 実際の例を進める前に、会話に関連する技術の特徴について簡単に説明しましょう。
テクノロジー
WebAssemblyテクノロジーは、Webに適したコードフォーマットを安全かつポータブルでダウンロードして実行することを目的としたイニシアチブです。 WebAssemblyはプログラミング言語ではありません。 これは、テキストおよびバイナリ形式の仕様を持つコンパイルターゲットです。 これは、C / C ++、Rust、Swiftなどの他の低レベル言語をWebAssemblyにコンパイルできることを意味します。 WebAssemblyは、ブラウザベースのJavaScriptと同じAPIへのアクセスを提供し、既存のテクノロジースタックにシームレスに統合します。 これにより、wasmはJavaアプレットのようなものと区別されます。 WebAssembly アーキテクチャは、すべての主要なWebブラウザの開発者とのコミュニティベースのコミュニティ努力の結果です。 Emscriptenは、コードをWebAssembly形式にコンパイルするために使用されます。
Emscriptenは、JavaScriptのLLVMバイトコンパイラです。 つまり、その助けを借りて、C / C ++またはJavaScriptの他の言語で書かれたプログラムをコンパイルすることができ、そのコードをLLVM形式に変換できます。 Emscriptenは、コードをWebに適した形式に移植するための一連のAPIを提供します。 このプロジェクトは長年にわたって使用されており、主にゲームをブラウザオプションに変換するために使用されています。 Emscriptenを使用すると、Asm.js標準に準拠するコードを生成するため、高いパフォーマンスを実現できます。これについては後述しますが、最近ではWebAssemblyのサポートに成功しています。
Asm.jsは、低レベルに最適化されたJavaScriptのサブセットであり、型付き配列を使用してメモリへの線形アクセスを提供し、データ型に関する情報を含む注釈をサポートします。 Asm.jsは、ソリューションのパフォーマンスを向上させます。 これも新しいプログラミング言語ではありません。したがって、ブラウザがサポートしていない場合、Asm.jsコードは通常のJavaScriptとして実行されます。つまり、その使用からパフォーマンスを向上させることはできません。
WebAssemblyは、2017年1月10日現在、 Chrome CanaryおよびFirefoxでサポートされています。 wasm-codeが機能するためには、設定で対応する機能を有効にする必要があります。 WebAssemblyのSafariサポートはまだ開発中です。 V8では、wasmはデフォルトで有効になっています。
V8エンジン、JavaScriptサポートの現在の状態、 Chrome Dev Summit 2016での WebAssemblyに関する興味深いビデオを次に示します。
モジュールの組み立てとロード
Cで書かれたプログラムをwasm形式に変換しましょう。 これを行うために、私はこの機会にスタンドアロン WebAssemblyモジュールを作成することにしました。 このアプローチでは、コンパイラの出力で、追加の補助.jsファイルなしで、WebAssemblyコードを含むファイルのみを取得します。
このアプローチは、 追加モジュール (サイドモジュール)Emscriptenの概念に基づいています 。 これらのモジュールは本質的に動的ライブラリに非常に似ているため、ここでそのようなモジュールを使用するのは理にかなっています。 たとえば、システムライブラリは自動的にそれらに接続するのではなく、コンパイラによって発行される自己完結型のコードブロックです。
$ emcc fibonacci.c -Os -s WASM=1 -s SIDE_MODULE=1 -o fibonacci.wasm
バイナリファイルを受け取ったら、ブラウザにダウンロードするだけです。 これを行うために、WebAssembly APIは、モジュールのコンパイルとインスタンス化に必要なメソッドを含むトップレベルのWebAssemblyオブジェクトを提供します。 これは、ユニバーサルブートローダーとして機能するAlon Zakaiの要点に基づいた簡単な方法です。
module.exports = (filename) => { return fetch(filename) .then(response => response.arrayBuffer()) .then(buffer => WebAssembly.compile(buffer)) .then(module => { const imports = { env: { memoryBase: 0, tableBase: 0, memory: new WebAssembly.Memory({ initial: 256 }), table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }) } }; return new WebAssembly.Instance(module, imports); }); }
最良の部分は、すべてが非同期的に発生することです。 まず、ファイルの内容を取得し、 ArrayBuffer形式のデータ構造に変換します。 バッファには、固定長の生のバイナリデータが含まれています。 これらを直接実行することはできません。そのため、次のステップでWebAssembly.compileメソッドにバッファが渡され、 WebAssembly.Moduleが返されます。その結果、インスタンスはWebAssembly.Instanceを使用して作成できます。
このすべてをより深く理解したい場合は、WebAssemblyを使用するバイナリ形式 aの説明をお読みください。
性能試験
wasmモジュールの使用方法、パフォーマンスをテストし、JavaScriptの速度と比較する方法を見てみましょう。 調査した関数の入力に数値40を送信します。テストコードは次のとおりです。
const Benchmark = require('benchmark'); const loadModule = require('./loader'); const {fiboJs, fiboJsRec, fiboJsMemo} = require('./fibo.js'); const suite = new Benchmark.Suite; const numToFibo = 40; window.Benchmark = Benchmark; //Benchmark.js uses the global object internally console.info('Benchmark started'); loadModule('fibonacci.wasm').then(instance => { const fiboNative = instance.exports._fibonacci; const fiboNativeRec = instance.exports._fibonacciRec; const fiboNativeMemo = instance.exports._fibonacciMemo; suite .add('Js', () => fiboJs(numToFibo)) .add('Js recursive', () => fiboJsRec(numToFibo)) .add('Js memoization', () => fiboJsMemo(numToFibo)) .add('Native', () => fiboNative(numToFibo)) .add('Native recursive', () => fiboNativeRec(numToFibo)) .add('Native memoization', () => fiboNativeMemo(numToFibo)) .on('cycle', (event) => console.log(String(event.target))) .on('complete', function() { console.log('Fastest: ' + this.filter('fastest').map('name')); console.log('Slowest: ' + this.filter('slowest').map('name')); console.info('Benchmark finished'); }) .run({ 'async': true }); });
そして結果はここにあります。 ちなみに、 このページでは、すべてを自分で試すことができます。
JS loop x 8,605,838 ops/sec ±1.17% (55 runs sampled) JS recursive x 0.65 ops/sec ±1.09% (6 runs sampled) JS memoization x 407,714 ops/sec ±0.95% (59 runs sampled) Native loop x 11,166,298 ops/sec ±1.18% (54 runs sampled) Native recursive x 2.20 ops/sec ±1.58% (10 runs sampled) Native memoization x 30,886,062 ops/sec ±1.64% (56 runs sampled) Fastest: Native memoization Slowest: JS recursive
Cプログラムから受信したwasmコード(テスト出力では「ネイティブ」と指定)は、通常のJavaScriptで記述された同様のコード(テスト出力では「JS」)よりも高速であることが明確にわかります。 最速の実装は、メモ化手法を使用してフィボナッチ数を見つけるためのwasm関数であり、最も遅い実装はJavaScriptの再帰関数です。
計算機で結果の上に座ると、次のことがわかります。
- 最高のC実装は、最高のJS実装よりも375%高速です。
- Cの最速のオプションはメモ化を使用します。 JSでは、ループを使用したアルゴリズムの実装です。
- Cで2番目に効率的な実装は、JSの最速バージョンよりも高速です。
- Cでのアルゴリズムの最も遅い実装は、JSでの最も遅いバージョンよりも338%高速です。
まとめ
WebAssemblyの機能と、このテクノロジで今日達成できることについての短い話を楽しんでいただけたことを願っています。 この資料の範囲外には、かなりの数のトピックがあります。その中には、コンパイル中の補助モジュールが作成されるwasmモジュール、コンパイルされたCコードとJSコード間のさまざまなやり取り方法、および動的リンクがあります。 あなたと私はいつか議論する可能性があります。 これで、WebAssemblyの実験を開始するために必要なものがすべて揃いました。 ちなみに、WebAssembly 開発者向けの公式ガイドもご覧ください 。
wasm領域の最新の開発状況を把握するには、 このページをプロジェクトの成果と開発計画に関する情報でブックマークしてください 。 Emscripten 変更ログを調べると便利です。
ところで、プロジェクトでWebAssemblyを活用する方法について既に考えましたか?