WebAssemblyを使用したクライアント側でのデータの処理





WebAssembly(略してWASM)は、事前にコンパイルされたバイナリコードをクライアント側のブラウザで実行するためのテクノロジです。これは2015年に最初に導入され、現在ほとんどの最新のブラウザーでサポートされています。



一般的な使用例の1つは、ファイルをサーバーに送信する前にクライアント側でデータを前処理することです。この記事では、これがどのように行われるかを理解します。



始める前に



WebAssemblyのアーキテクチャと一般的な手順については、ここここで詳しく説明ます基本的な事実のみを取り上げます。



WebAssemblyの操作は、コンパイルされたコードをクライアント側で実行するために必要なアーティファクトを事前にアセンブルすることから始まります。それらの2つがあります。バイナリWASMファイル自体と、そこにエクスポートされたメソッドを呼び出すことができるJavaScriptレイヤーです。



コンパイル用の最も単純なC ++コードの例



#include <algorithm>

extern "C" {
int calculate_gcd(int a, int b) {
  while (a != 0 && b != 0) {
    a %= b;
    std::swap(a, b);
  }
  return a + b;
}
}


アセンブリには、Emscriptenが使用されます。これには、そのようなコンパイラのメインインターフェイスに加えて、仮想マシンの構成とエクスポートされたメソッドを設定するための追加のフラグが含まれています。最も単純な起動は次のようになります。



em++ main.cpp --std=c++17 -o gcd.html \
    -s EXPORTED_FUNCTIONS='["_calculate_gcd"]' \
    -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'


* .htmlファイル をオブジェクトとして指定することにより、jsコンソールでも単純なhtmlマークアップを作成するようにコンパイラーに指示します。ここで、受信したファイルでサーバーを起動すると、_calculate_gcdを実行できるこのコンソールが表示されます







情報処理



C ++で記述されたライブラリを使用したlz4圧縮の簡単な例を使用して分析してみましょう。サポートされている多くの言語はそれだけではないことに注意してください



例の単純さといくつかの合成的な性質にもかかわらず、それはデータを操作する方法のかなり有用な例証です。同様に、サーバーに送信する前の画像の前処理、音声の圧縮、さまざまな統計のカウントなど、クライアントの能力が十分であるアクションを実行できます。



コード全体はここにあります。



C ++パート



私たちは、使用LZ4の既製の実装を次に、メインファイルは非常に簡潔に見えます。



#include "lz4.h"

extern "C" {

uint32_t compress_data(uint32_t* data, uint32_t data_size, uint32_t* result) {
  uint32_t result_size = LZ4_compress(
        (const char *)(data), (char*)(result), data_size);
  return result_size;
}

uint32_t decompress_data(uint32_t* data, uint32_t data_size, uint32_t* result, uint32_t max_output_size) {
  uint32_t result_size = LZ4_uncompress_unknownOutputSize(
        (const char *)(data), (char*)(result), data_size, max_output_size);
  return result_size;
}

}


ご覧のとおり、lz4を使用してライブラリから対応するメソッドを内部的に呼び出す外部関数externキーワードを使用宣言するだけです。



一般的に言って、私たちの場合、ファイルは役に立ちません。lz4.hのネイティブインターフェイスをすぐに使用できますただし、より複雑なプロジェクト(たとえば、異なるライブラリの機能を組み合わせる)では、使用されるすべての機能をリストするこのような共通のエントリポイントがあると便利です。



次に、すでに述べたEmscriptenコンパイラを使用してコードをコンパイルします



em++ main.cpp lz4.c -o wasm_compressor.js \
    -s EXPORTED_FUNCTIONS='["_compress_data","_decompress_data"]' \
    -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' \
    -s WASM=1 -s ALLOW_MEMORY_GROWTH=1


受信したアーティファクトのサイズは驚くべきものです。



$ du -hs wasm_compressor.*
112K    wasm_compressor.js
108K    wasm_compressor.wasm


JSファイルレイヤーを開くと、次のようなものが表示されます。







コメントからサービス機能まで、ほとんど使用されていない不要なものがたくさん含まれています。この状況は、-O2フラグを追加することで修正できます。Emscriptenコンパイラには、jsコードの最適化も含まれています。



その後、jsコードはより良く見えます:







クライアントコード



なんとかしてクライアント側ハンドラーを呼び出す必要があります。まずFileReader、を介してユーザーから提供されたファイルをロードします。生データをプリミティブに保存しますUint8Array



var rawData = new Uint8Array(fileReader.result);


次に、ダウンロードしたデータを仮想マシンに転送する必要があります。これを行うには、最初に_mallocメソッドを使用して必要なバイト数を割り当て、次にsetメソッドを使用してそこにJS配列をコピーします。便宜上、このロジックをarrayToWasmPtr(配列)関数に分けてみましょう。




function arrayToWasmPtr(array) {
  var ptr = Module._malloc(array.length);
  Module.HEAP8.set(array, ptr);
  return ptr;
}


仮想マシンのメモリにデータをロードした後、何らかの方法で処理から関数を呼び出す必要があります。しかし、この関数を見つける方法は?cwrapメソッドは、必要な関数の名前を指定する最初の引数、2番目の戻り型、3番目の入力引数のリストを示します。




compressDataFunction = Module.cwrap('compress_data', 'number', ['number', 'number', 'number']);


最後に、仮想マシンから完成したバイトを返す必要があります。これを行うには、メソッドを使用してそれらをJS配列にコピーする別の関数を記述します。subarray



function wasmPtrToArray(ptr, length) {
  var array = new Int8Array(length);
  array.set(Module.HEAP8.subarray(ptr, ptr + length));
  return array;
}


着信ファイルを処理するための完全なスクリプトはここにありますファイルアップロードフォームとwasmアーティファクトを含むHTMLマークアップはここにアップロードされます



結果



ここで プロトタイプを試してみることができます



その結果、WASMを使用した作業バックアップが作成されます。マイナス面のうち、テクノロジーの現在の実装では、仮想マシンに割り当てられたメモリを解放することはできません。これにより、1つのセッションで多数のファイルがロードされるときに暗黙のリークが発生しますが、新しいメモリを割り当てる代わりに既存のメモリを再利用することで修正できます。










All Articles