
WebAssemblyの発表は2015年に行われました-しかし、今では、何年も経った今でも、実稼働で自慢できる人はほとんどいません。 そのような経験に関する資料はすべてより価値があります。実際にそれをどのように生きるかについての直接的な情報はまだ不足しています。
HolyJSカンファレンスで、WebAssemblyを使用した経験に関するレポートが聴衆から高い評価を受け、現在、このレポートのテキスト版がHabr用に特別に準備されています(ビデオも添付されています)。
私の名前はアンドレイです。WebAssemblyについて説明します。 前世紀にウェブに携わり始めたと言えますが、私は謙虚なので、それは言いません。 この間、私はなんとかバックエンドとフロントエンドの両方で作業することができ、少しのデザインさえも描きました。 今日、WebAssembly、C ++、その他のネイティブなものに興味があります。 私はタイポグラフィも大好きで、古いテクノロジーを収集しています。
最初に、チームと私がプロジェクトにWebAssemblyを実装した方法について説明し、次にWebAssemblyから何かが必要かどうかを議論し、それを自分で実装したい場合にいくつかのヒントで終わります。
WebAssemblyの実装方法
私はInetraで働いており、ノボシビルスクにあり、独自のプロジェクトをいくつか行っています。 それらの1つはByteFogです。 これは、ユーザーにビデオを配信するためのピアツーピアテクノロジーです。 私たちの顧客は、大量のビデオを配信するサービスです。 彼らには問題があります。たとえば、誰かの記者会見やスポーツイベントなどの人気のあるイベントが発生したとき、準備を整える方法、たくさんのクライアントがサーバーに寄りかかって、サーバーが悲しいです。 現時点では、顧客のビデオ品質は非常に低くなっています。
しかし、誰もが同じコンテンツを見ています。 ユーザーの近隣のデバイスにビデオの断片を共有するように依頼してみましょう。サーバーをアンロードして帯域幅を節約し、ユーザーはより良い品質でビデオを受信します。 これらのクラウドは、当社のテクノロジーであり、ByteFogプロキシサーバーです。

ビデオを表示できるすべてのデバイスにインストールする必要があるため、Windows、Linux、Android、iOS、Web、Tizenなど、非常に幅広いプラットフォームをサポートしています。 これらすべてのプラットフォームで単一のコードベースを使用するために選択する言語は何ですか? C ++を選択したのは、最も利点があることが判明したためです:-Dさらに深刻なことは、C ++の専門知識があり、実際に高速な言語であり、移植性においては、Cに次ぐ可能性があることです。
かなり大きなアプリケーション(900クラス)を取得しましたが、うまく機能します。 WindowsおよびLinuxでは、ネイティブコードにコンパイルします。 AndroidおよびiOSの場合、アプリケーションに接続するライブラリを構築しています。 Tizenについては別の機会に話をしますが、以前はWebでブラウザプラグインとして働いていました。
これは、Netscape Plugin APIテクノロジーです。 名前が示すように、非常に古く、欠点もあります。システムに非常に広範囲にアクセスできるため、ユーザーコードがセキュリティ上の問題を引き起こす可能性があります。 これがおそらく2015年にChromeがこのテクノロジーのサポートをオフにし、すべてのブラウザーがこのフラッシュモブに参加した理由です。 そのため、私たちはほぼ2年間、Webバージョンなしで放置されていました。
2017年、新たな希望が訪れました。 ご想像のとおり、これはWebAssemblyです。 その結果、アプリケーションをブラウザに移植するタスクを設定しました。 FirefoxとChromeのサポートは既に春に登場していたため、2017年の秋にはEdgeとSafariが姿を消しました。
バグの数が2倍にならないように、2倍にしたくないビジネスロジックがたくさんあるため、既製のコードを使用することが重要でした。 コンパイラーEmscriptenを使用してください。 必要なことを行います-plusアプリケーションをブラウザにコンパイルし、ブラウザのネイティブアプリケーションに馴染みのある環境を再作成します。 EmscriptenはそのようなBrowser ++ for C ++コードであると言えます。 また、オブジェクトをC ++からJavaScriptに、またはその逆に転送できます。 最初に考えたのは、Emscriptenを使用してコンパイルするだけで、すべてが機能することです。 もちろん、そうではありませんでした。 これから、熊手に沿った旅が始まりました。
最初に出会ったのは依存関係でした。 コードベースにはいくつかのライブラリがありました。 それらをリストするのは意味がありませんが、理解している人のために、Boostがあります。 これはクロスプラットフォームコードを記述できる大きなライブラリですが、それを使用してコンパイルを構成することは非常に困難です。 できるだけ少ないコードをブラウザーにドラッグしたかったのです。
Bytefogのアーキテクチャ
その結果、コアを特定しました。これは、メインのビジネスロジックを含むプロキシサーバーであると言えます。 このプロキシサーバーは、2つのソースからデータを取得します。 最初の主要なものはHTTP、つまり動画配信サーバーへのチャネル、2番目はP2Pネットワーク、つまり他のユーザーからの別の同じプロキシへのチャネルです。 ユーザーに高品質のコンテンツを表示することがタスクであるため、データは主にプレーヤーに提供されます。 リソースが残っている場合、他のユーザーがダウンロードできるように、コンテンツをP2Pネットワークに配信します。 内部には、すべての魔法を行うスマートキャッシュがあります。

これをすべてコンパイルすると、WebAssemblyがブラウザサンドボックスで実行されるという事実に直面します。 つまり、JavaScriptが提供する以上のことはできないということです。 ネイティブアプリケーションは、ファイルシステム、ネットワーク、乱数など、プラットフォーム固有の多くのものを使用します。 これらの機能はすべて、ブラウザが提供するものを使用してJavaScriptで実装する必要があります。 このプレートには、かなり明らかな代替品がリストされています。

これを可能にするには、ネイティブアプリケーションのネイティブ機能の実装を見送り、そこにインターフェイスを挿入する、つまり特定の境界線を引く必要があります。 次に、これをJavaScriptで実装し、ネイティブ実装を終了します。すでにアセンブリ中に必要な実装が選択されています。 それで、私たちは建築を見て、この境界線を描くことができるすべての場所を見つけました。 偶然にも、これはトランスポートサブシステムです。

そのような場所ごとに、仕様を定義しました。つまり、契約を修正しました。どのメソッドとなるか、どのパラメーターを持つか、どのデータ型かなどです。 これを行うと、各開発者がそれぞれの側で並行して作業できます。
結果は何ですか? プロバイダーからのメインビデオ配信チャネルを通常のAJAXに置き換えました。 人気のあるHLS.jsライブラリを通じてプレーヤーにデータを発行しますが、必要に応じて他のプレーヤーと統合する基本的な可能性があります。 P2Pレイヤー全体をWebRTCに置き換えました。

コンパイルの結果、いくつかのファイルが取得されます。 最も重要なのは、バイナリ.wasmです。 これには、ブラウザが実行するコンパイル済みバイトコードが含まれ、すべてのC ++レガシーが含まれます。 しかし、それ自体では機能せず、いわゆる「グルーコード」が必要であり、コンパイラによっても生成されます。 グルーコードはバイナリファイルをダウンロードしており、これらのファイルの両方を実稼働環境にアップロードします。 デバッグの目的で、アセンブラーのテキスト表現、.wastファイルおよびソースマップを生成できます。 それらは非常に大きくなる可能性があることを理解する必要があります。 私たちの場合、それらは100メガバイト以上に達しました。
バンドルの収集
グルーコードを詳しく見てみましょう。 これは通常の古き良きES5であり、単一のファイルにアセンブルされます。 これをWebページに接続すると、インスタンス化されたすべてのwasmモジュールを含むグローバル変数が作成され、APIへのリクエストを受け入れる準備が整います。
ただし、ユーザーが使用するライブラリにとって、別のファイルを含めることはかなり深刻な問題です。 すべてを1つのバンドルにまとめたいと思います。 このために、Webpackと特別なコンパイルオプションMODULARIZEを使用します。
接着剤コードを「モジュール」パターンでラップし、それを拾うことができます。ES5で記述している場合、importまたはrequireを使用します。Webpackはこの依存関係を冷静に理解します。 Babelに問題がありました-彼は大量のコードが好きではありませんでしたが、これはES5コードであり、転置する必要はありません。無視するために追加するだけです。
ファイルの数を追跡するために、SINGLE_FILEオプションを使用することにしました。 コンパイルの結果として生成されたすべてのバイナリをBase64形式に変換し、文字列として接着コードにプッシュします。 素晴らしいアイデアのように聞こえますが、その後、バンドルのサイズは100メガバイトになりました。 WebpackもBabelも、ブラウザもそのようなボリュームでは動作しません。 とにかく、ユーザーに100メガバイトの読み込みを強制しませんか?!
考えてみれば、このオプションは必要ありません。 接着剤コードは、バイナリファイルを単独でダウンロードします。 彼はHTTPを介してこれを行うため、箱から出してキャッシュを取得し、必要なヘッダーを設定できます。たとえば、圧縮を有効にし、WebAssemblyファイルは完全に圧縮されます。
しかし、最もクールなテクノロジーはストリーミングコンパイルです。 つまり、WebAssemblyファイルは、サーバーからのダウンロード中に、データが到着するとブラウザーで既にコンパイルされている可能性があり、これによりアプリケーションの読み込みが大幅に高速化されます。 一般に、すべてのWebAssemblyテクノロジーは、大規模なコードベースのクイックスタートに重点を置いています。
テナブル
モジュールのもう1つの問題は、Thenableオブジェクトである、つまり.then()メソッドがあることです。 この関数を使用すると、モジュールの起動時にコールバックをハングさせることができ、非常に便利です。 しかし、私はインターフェースがPromiseに一致することを望みます。 ThenableはPromiseではありませんが、大丈夫です。自分でまとめましょう。 そのような単純なコードを書きましょう:
return new Promise((resolve, reject) => { Module(config).then((module) => { resolve(module); }); });
Promiseを作成し、モジュールを起動し、コールバックとしてresolve関数を呼び出し、そこにインストールしたモジュールを渡します。 すべてが明らかであるように見え、すべてが正常であり、起動しています-何かが間違っている、ブラウザがフリーズしている、DevToolsがハングしている、プロセッサがコンピュータ上で熱くなっています。 私たちは何も理解していません-ある種の再帰または無限ループ。 デバッグは非常に難しく、JavaScriptを中断すると、EmscriptenモジュールのThen関数になりました。
Module['then'] = function(func) { if (Module['calledRun']) { func(Module); } else { Module['onRuntimeInitialized'] = function() { func(Module); }; }; return Module; };
もっと詳しく見てみましょう。 プロット
Module['onRuntimeInitialized'] = function() { func(Module); };
コールバックをハングさせます。 ここではすべてが明確です。コールバックを呼び出す非同期関数です。 私たちが望むようにすべて。 この機能には別の部分があります。
if (Module['calledRun']) { func(Module);
モジュールが既に起動しているときに呼び出されます。 その後、コールバックはすぐに同期的に呼び出され、モジュールはパラメーターで渡されます。 これはPromiseの振る舞いを模倣しており、私たちが期待していることのようです。 しかし、何が悪いのでしょうか?
ドキュメントを注意深く読んだ場合、Promiseには非常に微妙な点があることがわかります。 Thenableを使用してPromiseを解決すると、ブラウザーはこのThenableから値を展開し、これを行うために.then()メソッドを呼び出します。 その結果、Promiseを解決し、それにモジュールを渡します。 ブラウザは次のように尋ねます:それからこれはオブジェクトですか? はい、これはThenableです。 次に、モジュールで.then()関数が呼び出され、解決関数自体がコールバックとして渡されます。
モジュールは、実行中かどうかを確認します。 すでに実行されているため、コールバックがすぐに呼び出され、同じモジュールが再度渡されます。 コールバックとして、resolve関数があり、ブラウザーは次のように尋ねます:これはThenableオブジェクトですか? はい、これはThenableです。 そして、すべてが再び始まります。 その結果、ブラウザが戻らない無限のサイクルに陥ります。

この問題に対するエレガントな解決策は見つかりませんでした。 その結果、解決する前に.then()メソッドを削除するだけで、これは機能します。
Emscripten
そのため、モジュールをコンパイルし、JSをアセンブルしましたが、何かが欠落しています。 おそらく、いくつかの有用な作業を行う必要があります。 これを行うには、データを転送し、JSとC ++の2つの世界を接続します。 どうやってやるの? Emscriptenには3つのオプションがあります。
- 1つ目は、ccallおよびcwrap関数です。 ほとんどの場合、WebAssemblyの一部のチュートリアルでそれらに会いますが、C ++の機能をサポートしていないため、実際の作業には適していません。
- 2番目はWebIDLバインダーです。 すでにC ++関数をサポートしているため、既に使用できます。 これは、たとえばW3Cがドキュメントのために使用する、深刻なインターフェイス記述言語です。 しかし、私たちはそれをプロジェクトに持ち込みたくなく、3番目のオプションを利用しました
- 組みます。 これはEmscriptenのオブジェクトを接続するネイティブな方法であり、C ++テンプレートに基づいており、C ++からJSへ、またはその逆に異なるエンティティを転送することで多くのことを行うことができます。
Embindでできること:
- JavaScriptコードからC ++関数を呼び出す
- C ++クラスからJSオブジェクトを作成する
- C ++コードから、ブラウザーAPIを使用します(何らかの理由で必要な場合は、たとえば、フロントエンドフレームワーク全体をC ++で記述できます)。
- 主なことは、C ++で説明されているJavaScriptインターフェイスを実装することです。
データ交換
最後の点は重要です。これはまさにこれが、アプリケーションを移植するときに常に行うアクションだからです。 したがって、私はそれについてさらに詳しく説明したいと思います。 これでC ++コードが作成されますが、怖がらないでください。TypeScriptに似ています:-D
スキームは次のとおりです。

C ++側には、ビデオをアップロードするために、たとえば外部ネットワークへのアクセスを許可するカーネルがあります。 以前はネイティブソケットを使用してこれを行っていましたが、これを行う何らかの種類のHTTPクライアントがありましたが、WebAssemblyにはネイティブソケットはありません。 どうにかして出て行く必要があるので、古いHTTPクライアントを切断し、この場所にインターフェイスを挿入し、通常のAJAXを使用してこのインターフェイスをJavaScriptに実装します。 その後、結果のオブジェクトをC ++に渡し、カーネルはそれを使用します。
get要求のみを行うことができる単純なHTTPクライアントを作成してみましょう。
class HTTPClient { public: virtual std::string get(std::string url) = 0; };
入力に対して、ダウンロードするURLを含む文字列を受け取り、出力に対して
クエリの結果を含む文字列。 C ++では、文字列にバイナリデータを含めることができるため、これはビデオに適しています。 Emscriptenを使用すると、ここに記述できます
そのような恐ろしいラッパー:

その中で、主なものは2つのものです-C ++側の関数の名前(緑色でマークしました)、およびJavaScript側の対応する名前(青色でマークしました)。 その結果、コミュニケーションの宣言を作成します。

レゴブロックのように機能し、そこから収集します。 クラスがあり、このクラスにはメソッドがあり、このクラスから継承してインターフェースを実装します。 以上です。 JavaScriptに移動して継承します。 これには2つの方法があります。 最初は拡張です。 これは、Backboneからの古き良き拡張と非常に似ています。

モジュールには、Emscriptenがコンパイルしたすべてのものが含まれており、エクスポートされたインターフェイスを持つプロパティがあります。 extendメソッドを呼び出し、このメソッドの実装でオブジェクトをそこに渡します。つまり、getメソッドでいくつかのメソッドが実装されます
AJAXを使用して情報を取得します。
出力では、extendは通常のJavaScriptコンストラクターを提供します。 好きなだけ呼び出して、必要な量のオブジェクトを生成できます。 しかし、1つのオブジェクトがあり、それをC ++側に渡したい場合があります。

これを行うには、何らかの方法でこのオブジェクトをC ++が理解できる型にバインドします。 これが、implement関数の機能です。 出力では、コンストラクターではなく、すぐに使用できるオブジェクトであるクライアントを提供し、C ++に戻すことができます。 これは、たとえば次のように実行できます。
var app = Module.makeApp(client, …)
アプリケーションを作成するファクトリーがあり、その依存関係をパラメーター(クライアントなど)に取り込むとします。 この関数が機能すると、アプリケーションのオブジェクトを取得します。このオブジェクトには、必要なAPIが既に含まれています。 あなたは反対を行うことができます:
val client = val::global(″client″); client.call<std::string>(″get″, val(...) );
C ++から直接、クライアントをグローバルブラウザスコープから取り出します。 さらに、クライアントの代わりに、コンソールから始まり、DOM API、WebRTCで終わる任意のブラウザーAPIを使用できます。 次に、このオブジェクトが持つメソッドを呼び出し、Emscriptenが提供するマジッククラスvalのすべての値をラップします。
バインドエラー
一般に、これですべてですが、開発を開始すると、エラーが発生します。 これらは次のようになります。

Emscriptenは私たちを助け、何が悪いのかを説明しようとします。 これがすべてまとめられている場合、それらが一致することを確認する必要があります(封印してバインドエラーを取得するのは簡単です)。
Embind構文は、フロントエンドベンダーだけでなく、C ++を扱う人々にとっても珍しいものです。 これは一種のDSLで、間違いを犯しやすいため、これに従う必要があります。 インターフェースについて言えば、JavaScriptで何らかのインターフェースを実装する場合、契約で説明したものと正確に一致する必要があります。
興味深いケースがありました。 C ++側のプロジェクトに関与していた同僚のJuraは、Extendを使用してモジュールをテストしました。 彼らは彼のために完璧に働いたので、彼はそれらをコミットし、私に渡しました。 implementsを使用して、これらのモジュールをJSプロジェクトに統合しました。 そして、彼らは私のために働いて停止しました。 私たちがそれを理解したとき、関数の名前をバインドするときにタイプミスが得られたことが判明しました。
名前が示すように、Extendはインターフェイスの拡張であるため、どこかに封印した場合、Extendはエラーをスローせず、新しいメソッドを追加したと判断します。
つまり、メソッド自体が呼び出されるまでバインディングエラーを隠します。 転送されたインターフェイスの正確性をすぐにチェックするため、あなたに合ったすべての場合に実装を使用することをお勧めします。 ただし、Extendが必要な場合は、各メソッドの呼び出しをテストでカバーして、混乱しないようにする必要があります。
拡張とES6
Extendのもう1つの問題は、ES6クラスをサポートしていないことです。 ES6クラスから派生したオブジェクトを継承する場合、Extendはその中のすべてのプロパティが列挙可能であることを期待しますが、ES6ではそうではありません。 メソッドはプロトタイプにあり、列挙可能:falseです。 このような松葉杖を使用して、プロトタイプを調べ、enumerableをオンにします:true:
function enumerateProto(obj) { Object.getOwnPropertyNames(obj.prototype) .forEach(prop => Object.defineProperty(obj.prototype, prop, {enumerable: true}) ) }
EmscriptenコミュニティでES6のサポートを改善することについての講演があるので、いつかそれを取り除くことができればと思います。
RAM
C ++について言えば、メモリについて言及するしかありません。 SD品質のビデオですべてを確認したところ、すべてが順調で、完璧に機能しました! FullHDテストを実行するとすぐに、メモリ不足エラーが発生しました。 モジュールの開始メモリ値を設定するTOTAL_MEMORYオプションがあります。 私たちは0.5ギガバイトを作りましたが、すべては問題ありませんが、メモリをすべての人に予約しているのに、だれもがFullHDコンテンツのサブスクリプションを持っているわけではないため、ユーザーにとっては非人道的です。
別のオプションがあります-ALLOW_MEMORY_GROWTH。 メモリを増やすことができます
必要に応じて徐々に。 これは次のように動作します。Emscriptenはデフォルトでモジュールに動作用に16メガバイトを与えます。 すべて使用すると、新しいメモリが割り当てられます。 古いデータはすべてそこにコピーされますが、新しいデータ用に同じ量のスペースが残っています。 これは、4 GBに達するまで発生します。
256メガバイトのメモリを割り当てたが、アプリケーションに十分な192があると思っていたことが確実にわかっている場合、メモリの残りは非効率的に使用されます。 それを強調表示し、ユーザーから取得しましたが、何もしません。 どういうわけかこれを避けたいです。 小さなトリックがあります:メモリを1.5倍に増やして作業を開始します。 次に、3番目のステップで192メガバイトに達します。これがまさに必要なものです。 その残りによってメモリ消費を削減し、不必要なメモリ割り当てを節約しました。さらに、時間がかかるようになりました。 したがって、これらのオプションの両方を一緒に使用することをお勧めします。
依存性注入
それがすべてだったように見えますが、その後、熊手はもう少し行きました。 依存性注入に問題があります。 依存関係が必要な最も単純なクラスを記述します。
class App { constructor(httpClient) { this.httpClient = httpClient } }
たとえば、HTTPクライアントをアプリケーションに渡します。 クラスプロパティに保存します。 すべてがうまくいくようです。
Module.App.extend( ″App″, new App(client) )
C ++インターフェイスから継承し、最初にオブジェクトを作成し、それに依存関係を渡し、次に継承します。 継承の時点で、Emscriptenはオブジェクトに対して信じられないようなことをします。 最も簡単な考え方は、古いオブジェクトを削除し、そのテンプレートに基づいて新しいオブジェクトを作成し、そこにすべてのパブリックメソッドをドラッグすることです。 しかし同時に、オブジェクトの状態は失われ、オブジェクトは形成されず、正しく機能しません。 この問題の解決は非常に簡単です。 継承段階の後に機能するコンストラクターを使用する必要があります。
class App { _construct(httpClient) { this.httpClient = httpClient this._parent._construct.call(this) } }
ほぼ同じことを行います。オブジェクトのフィールドに依存関係を格納しますが、これは継承後に判明したオブジェクトです。 C ++側にある親オブジェクトにコンストラクター呼び出しを転送することを忘れないでください。 最後の行は、ES6のsuper()メソッドに類似しています。 この場合、継承は次のように行われます。
const appConstr = Module.App.extend( ″App″, new App() ) const app = new appConstr(client)
まず、継承してから、依存関係が既に渡されている新しいオブジェクトを作成します。これは機能します。
ポインタートリック
別の問題は、C ++からJavaScriptへのポインターによるオブジェクトの受け渡しです。 すでにHTTPクライアントを作成しました。 簡単にするために、1つの重要な詳細を見落としています。
std::string get(std::string url)
メソッドはすぐに値を返します。つまり、要求は同期する必要があることがわかります。 しかし結局のところ、AJAXに対するAJAXリクエストは非同期であるため、実際にはメソッドは何も返さないか、リクエストIDを返すことができます。 しかし、誰かに答えを返してもらうために、C ++からのコールバックがある2番目のパラメーターとしてリスナーを渡します。
void get(std::string url, Listener listener)
JSでは、次のようになります。
function get(url, listener) { fetch(url).then(result) => { listener.onResult(result) }) }
このリスナーオブジェクトを取得するget関数があります。 ファイルのダウンロードを開始し、コールバックを切ります。 ファイルがダウンロードされると、リスナーから目的の関数を取得し、結果を渡します。
計画は良いように見えますが、get関数が完了すると、すべてのローカル変数が破棄され、それらとともに関数パラメーター、つまりポインターが破棄され、ランタイムemscriptenはC ++側のオブジェクトを破棄します。
その結果、行listener.onResult(result)の呼び出しに関しては、リスナーは存在しなくなり、それにアクセスすると、アプリケーションのクラッシュにつながるメモリアクセスエラーが発生します。
これを避けたいのですが、解決策はありますが、それを見つけるのに数週間かかりました。
function get(url, listener) { const listenerCopy = listener.clone() fetch(url).then((result) => { listenerCopy.onResult(result) listenerCopy.delete() }) }
ポインターを複製する方法があることがわかりました。 何らかの理由で文書化されていませんが、正常に機能し、Emscriptenポインターの参照カウントを増やすことができます。 これにより、クロージャーでそれを一時停止することができ、コールバックを起動すると、リスナーはこのポインターでアクセスでき、必要に応じて作業できます。
最も重要なことは、このポインタを削除することを忘れないことです。さもないと、メモリリークエラーが発生します。これは非常に悪いことです。
メモリへの高速書き込み
ビデオをダウンロードするとき、これは比較的大量の情報であり、メモリと時間の両方を節約するために、データを前後にコピーする量を減らしたいと思います。 JavaScriptから大量の情報をWebAssemblyメモリに直接書き込む方法には、1つのトリックがあります。
var newData = new Uint8Array(…); var size = newData.byteLength; var ptr = Module._malloc(size); var memory = new Uint8Array( Module.buffer, ptr, size ); memory.set(newData);
newDataは、型付き配列の形式のデータです。 その長さを取得し、WebAssemblyモジュールから必要なサイズのメモリの割り当てを要求できます。 malloc関数は、WebAssembly内のすべてのメモリを含む配列のインデックスであるポインタを返します。 JavaScript側から見ると、ArrayBufferのように見えます。
次のステップまでに、特定の場所から必要なサイズのこのArrayBufferにウィンドウを切り取り、そこにデータをコピーします。 集合演算にはコピーセマンティクスがあるという事実にもかかわらず、プロファイラーでこのセクションを見たとき、長いプロセスは見ませんでした。 ブラウザーは、移動セマンティクスの助けを借りてこの操作を最適化する、つまり、あるオブジェクトから別のオブジェクトにメモリの所有権を移すと思います。
また、このアプリケーションでは、メモリのコピーを節約するために、移動セマンティクスにも依存しています。
Adblock
むしろ、変更に関するAdblockの興味深い問題です。 ロシアでは、人気のあるすべてのブロッカーがRU Adlistのサブスクリプションを受け取り、サードパーティのサイトからWebAssemblyをダウンロードすることを禁止するような素晴らしいルールがあります。 たとえば、CDNを使用します。

解決策は、CDNを使用するのではなく、すべてをドメインに保存することです(これは私たちには適していません)。 または、この規則に適合しないように.wasmファイルの名前を変更します。 あなたはまだこれらの仲間のフォーラムに行き、このルールを削除するように彼らを説得しようとすることができます。 この方法で鉱山労働者と戦うことで彼らは正当化されると思いますが、鉱山労働者がファイルの名前を変更することを推測できない理由はわかりません。
生産
その結果、生産に入りました。 はい、それは簡単ではありませんでした、それは8ヶ月かかりました、そして、私はそれが価値があるかどうか自問したいです。 私の意見では-それは価値があった:
インストールする必要はありません
プログラムをインストールすることなく、コードがユーザーに配信されるようになりました。 ブラウザープラグインを作成したとき、ユーザーはそれをダウンロードしてインストールする必要がありました。これは、テクノロジーの配布のための巨大なフィルターです。 現在、ユーザーはサイトでビデオを見るだけで、機械全体がボンネットの下で機能し、すべてが複雑であることを理解していません。 ブラウザは、画像や.cssなどのコードを含む追加ファイルをダウンロードするだけです。
異なるプラットフォームでの統一されたコードベースとデバッグ
同時に、単一のコードベースを維持することができました。 異なるプラットフォーム上で同じコードをねじることができますが、一方のプラットフォームでは見えなかったバグがもう一方のプラットフォームで発生することが繰り返し発生しました。 したがって、さまざまなプラットフォームのさまざまなツールを使用して、隠れたバグを検出できます。
クイックリリース
簡単なWebアプリケーションとしてリリースでき、新しいリリースごとにC ++コードを更新できるため、クイックリリースができました。 新しいプラグイン、モバイルアプリケーション、SmartTVアプリケーションをリリースする方法とは異なります。 リリースは私たちだけに依存します。必要なときにリリースされます。
クイックフィードバック
そして、それは迅速なフィードバックを意味します。何か問題が発生した場合、日中に問題があることを発見し、それに対応することができます。
私は、これらの問題はすべてこれらの利点に値すると信じています。 誰もがC ++アプリケーションを持っているわけではありませんが、もしあなたがそれをブラウザに入れたいなら、WebAssemblyはあなたにとって100%のユースケースです。
適用先
誰もがC ++で書いているわけではありません。 ただし、WebAssemblyで使用できるのはC ++だけではありません。 はい、これは歴史的に、初期のMozillaテクノロジーであるasm.jsでまだ利用可能な最初のプラットフォームです。 ちなみに、したがって、かなり良いツールがあります。 彼らは技術自体よりも古いです。
さび
また、Mozillaによって開発されている新しいRust言語は、ツールの点でC ++に追いついて追いついています。 すべてがWebAssemblyの最もクールな開発プロセスを作成するという点に至ります。
Lua、Perl、Python、PHPなど
インタープリターはC ++で記述されているため、ほとんどすべての言語がWebAssemblyで使用できます。インタープリターはWebAssemblyにコンパイルされているだけで、ブラウザーでPHPをねじることができます。
行く
バージョン1.11では、WebAssemblyでコンパイルのベータバージョンを作成し、2.0ではリリースサポートを約束しています。 WebAssemblyはガベージコレクターをサポートしておらず、Goはマネージメモリ言語であるため、それらのサポートは後で登場しました。 そのため、ガベージコレクターをWebAssemblyの下にドラッグする必要がありました。
コトリン/ネイティブ
コトリンと同じ話の周り。 コンパイラは実験的なサポートを提供していますが、ガベージコレクターで何かをする必要もあります。 どのようなステータスがあるのかわかりません。
3Dグラフィックス
他に何が考えられますか? , — 3D-. , , asm.js WebAssembly . , WebAssembly.

, : , , . , .

. , , , , . , , ; — .

, Google Chrome, , WebAssembly-. npm- , Wasm, JS. , ++ - — .
HunSpell — Wasm .
— « ». , - , — OpenSSL. WebAssembly. OpenSSL — , , .
use case wotinspector.com. World of Tanks. , , , , , .
— . , , . , , - ++, WebAssembly, ( , ).
. , , . . , , , , . . .
図書館
, , ++. , FFmpeg, . , ffmpeg. . , , , , .

— . OpenCV — , WebAssembly, . PDF. SQLite, SQL. SQLite WebAssembly Emscripten, .
Node.js

WebAssembly, Node.js. , Sass — css. Ruby, ++ ( libsass). , Webpack', Node.js.
node-sass , JS- .
, , . . :

, node-sass 100 . , ( ) . WebAssembly : , WebAssembly .
Node. , WebAssembly
libsass-asm . , . WebAssembly …
Figma — web-. - Sketch, , . ++ ( ), asm.js. , .

WebAssembly, , 3 . , .
Visual Studio Code, , Electron, , , Node-sass. , Node, . , , , WebAssembly.

— AutoCAD. 30 , ++, . , , - JavaScript, , . WebAssembly
AutoCAD - , 5 .
, , , , , , , , . FFMpeg — , — QEMU. , , KVM, .

2011
QEMU . , . ,
Linux , Linux-, , - .
, . bash, , Linux.
— GUI . . , , …

, , - .
Windows 2000 , , 18 , . , Chrome ( FireFox).
, WebAssembly , , , , .
, WebAssembly. , — , . — , .

, C++ web-. , , — . — , , , .
, . , C++, JavaScript, . , C++. , JS C++, .
— .

CI Pipeline
? JS- , Webpack. , , ( ), JS. webpack watch, , .

デバッグ
, . , , .
Chrome DevTools, Sources wasm-. ( - ), , , .

, , : «, , , , , !». , embedded-, , - .
: -g4 wast- , .

, 100 ( FAR). — , Chrome. E:/_work/bfg/bytefrog/… — . , ++ . , SourceMap!
SourceMap
, .
- Firefox.
- --sourcemap-base=http://localhost , SourceMap -, .
- HTTP.
- .
- Windows «:» . .
. CMake , URL -. : wast- , . , .
, :

++ . ! , , stack trace, . , wasm- stack trace, , , , , .

, — SourceMap . , , . , .

«var0».

, . , SourceMap, , .
. Chrome, Firefox. Firefox — «» , , .

Chrome ( , , Mangled ), , , , .

性能
. , :
- . runtime, . ++ Rust Go.
- JS — Wasm. , JS Wasm. -, , . , .
- . , , , .
- Wasm . Wasm , JS. WebAssembly , .
- JS.
: .
- wasp_cpp_bench
- Chrome 65.0.3325.181 (64-bit)
- Core i5-4690
- 24gb ram
- 5 ; max min;
. JS — , .

++, , - . Grayscale. C++ , . ( ), , JS. , , , ++, .
Sentry, — wasm. , traceKit, Sentry — Raven, — , , wasm . , , , pull request, npm install JS-.

. production, , . debug-, , :

合計
- WebAssembly , .
- — . 8 , C++, , .
- , , WebAssembly — .
- — JS. JS- , «» , , .
, :
- Emscripten Embind. .
- - Emscripten — . , , 3000 Emscripten.
- Sentry.
- Firefox.
ご清聴ありがとうございました! .

HolyJS, : 24-25 HolyJS . (, Node.js Ryan Dahl!), — 1 .