WebAssemblyとJavaScriptの処理速度を比較してみる


はじめに

前回に、C++からJavaScriptの関数を呼び出す方法を調べました。WebAssemblyはJavaScriptより早いと言われていますので試してみました。

環境

  • windows 10
  • python:3.6.5
  • emcc:1.39.16
  • clang:11.0.0

実験ブラウザ

  • Google Chrome:83.0.4103.106

純粋なループの比較

まずは、それぞれの中でループをさせてみました。また、C++については最適化オプションをそれぞれ試してみました。

C/C++ファイルの作成

純粋にループして、ループ内で加算した結果をJavaScriptに返却させます。

cmain.cpp

#include <emscripten/emscripten.h>

int main() {
    int count = 0;
    for(int i = 0; i < 100000000; i++){
        count = count + 1;
    }
    return count;
}

C/C++ファイルのコンパイル

なるべく条件を同じようにしたいため、最適化オプションのみ変更してコンパイルしました。

コマンド


>em++ cmain.cpp -o cmain.wasm

>em++ cmain.cpp -O1 -o cmain01.wasm

>em++ cmain.cpp -O2 -o cmain02.wasm

>em++ cmain.cpp -O3 -o cmain03.wasm

コマンドが終わると同フォルダ内にcmain.wasmが4つできているはずです。

wasmを呼び出すhtmlを作成する

html読み込み時にwasmファイルを読み込み、ボタンクリック時には関数の呼び出しだけするようにしました。
その関数のはじめと終わりからかかったミリ秒を出しました。長くなったので例は一つだけ書きました。

htmlの作成

ボタンとループ回数、かかったミリ秒を表示しています。

my_hello.html

<body>
  <div>
    <input type="button" id="j_view" value="JavaScript実行" />
    <label>ループ回数:</label><input type="text" id="j_output"/>
    <label>関数にかかった時間:</label><input type="text" id="j_time"/>
  </div>
  ~~略~~

クリック時処理

クリック時には関数の前と後の時間を取ってミリ秒を出しています。JavaScriptとの比較なのでJavaScriptのクリック時にはここでループします。

my_hello.html

  <script>
    ~~~~
    $('#j_view').click(function() {
      var startTime = performance.now();
      var count = 0;
      for (var i = 0; i < 100000000; i++) {
        count = count + 1
      }
      $('#j_output').val(count);
      var endTime = performance.now();
      $('#j_time').val(endTime - startTime);
    });      
    $('#c_view').click(function() {
      var startTime = performance.now();
      $('#c_output').val(c_main.instance.exports.main());
      var endTime = performance.now();
      $('#c_time').val(endTime - startTime);
    });
      ~~~~

実行

サーバを再起動して上のhtmlを実行します。

回数 JavaScript C++最適化なし C++最適化01 C++最適化02 C++最適化03
1 115.300 318.805 0.155 0.089 0.085
2 124.925 312.934 0.040 0.070 0.039
3 195.660 263.459 0.055 0.039 0.040
4 185.394 262.719 0.054 0.049 0.044
5 163.040 201.630 0.105 0.219 0.100

結果を見るとJavaScriptの方が最適化なしのC++より速いことが意外でした。
それ以外は、最適化を強くするたびに処理速度が速くなっています。

html操作の比較

C++からJavaScriptの関数を呼び出す方法を調べたのでJavaScriptとC++から表を作成したときの速度を試してみました。

C/C++ファイルの作成

2重ループにして、行と列をそれぞれ50回ずつ追加していきます。add_tradd_tdの2つのJavaScriptの関数をC++に公開しています。

cmain.cpp

#include <emscripten/emscripten.h>

extern "C" {
    extern void add_tr();
    extern void add_td(int x);
}

int main() {
    for(int i = 0; i < 50; i++){
        add_tr();
        for(int j = 0; j < 50; j++){
            add_td(j);
        }
    }
    return 0;
}

wasmを呼び出すhtmlを作成する

html読み込み時にwasmファイルを読み込み、ボタンクリック時には関数の呼び出しだけするようにしました。

htmlの作成

基本は先ほどと同じです。今回はC++にも公開している表を追加する関数を追加しています。クリック後の処理ではmain関数を呼び出しと関数の時間を表示しているだけです。一応表を削除する機能も入れて表を作成するたびにクリアしています。

my_hello.html

  <script>
    function add_tr(arg){
      $("#view_table tbody").append('<tr></tr>')
    };
    function add_td(arg){
      $("#view_table tbody tr:last").append('<td>' + arg + '</td>')
    };
    var importObject = { 
      wasi_snapshot_preview1: { proc_exit:args => console.log(args) }, 
      env: {
        add_tr: add_tr,
        add_td: add_td,
      } 
    }
    ~~~~~~

実行

サーバを再起動して上のhtmlを実行します。

回数 JavaScript C++最適化01 C++最適化02 C++最適化03
1 393.544 261.590 252.309 264.475
2 280.120 281.484 292.290 277.265
3 279.630 270.030 283.014 339.459
4 352.980 270.220 290.875 361.335
5 271.829 293.194 281.879 339.210

結果を見るとJavaScriptとC++は余り変わらないことがわかりました。ループのたびにJavaScriptの関数をC++から呼び出しているので下手したらC++の方が遅くなると思っていましたが、あんまり変わりませんでした。

おわりに

JavaScriptとC++の処理速度を比較してみました。予想していた通りにC++内で閉じた処理はC++の方が早く、JavaScriptと逐次連携する処理はJavaScriptと速度は変わりませんでした。まだ、WebAssemblyは発展途上の技術らしいのでまだまだ結果は変わっていくと思いますが、現状では配列などのデータの整理や計算のためにC++を使用して画面の描画に関してはJavaScriptにするすみわけが良いのかと思います。C++は最適化をしないときはどちらのケースも遅くなったので最適化は必須な気がします。