WebAssemblyとJavaScriptの最適化による処理速度を比較してみる


はじめに

前回のコメントで「サンプルが簡単すぎてCの最適化が早いのは当然」や「JavaScriptも最適化がある」と言ったことを教えてもらえたので再度比較してみました。

環境

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

実験ブラウザ

  • Google Chrome:83.0.4103.106

純粋なループの比較

前回と同様にそれぞれの中でループをさせてみました。JavaScriptの最適化はprepackとClosure Compilerの2つを使いました。

C/C++ファイルの作成とコンパイル

前回と全く同じなので省略します。

htmlの作成する

前回とほぼ同じなので省略します。

JavaScriptの作成と最適化

最適化用JavaScriptの作成

今回は最適化するためにループ部分だけ別ファイルにします。

jsmod.js

function loop_func_ori(){
    var count = 0;
    for(var i = 0; i < 100000000; i++){
        count = count + 1;
    }
    return count;
}

最適化ツール(prepack)のインストール

npmを使用してprepackをインストールします。


npm install prepack 

インストールしたフォルダ内のnode_modulesにprepackが追加されています。

JavaScriptの最適化(prepack)

prepackコマンドを使用して最適化を行います。


>.\node_modules\.bin\prepack jsmod.js --out jsmod-prepa.js
Prepacked source code written to jsmod-prepa.js.

--outで指定したファイルに最適化後のJavaScriptができます。

jsmod-prepa.js

var loop_func;
(function () {
  var _$0 = this;

  var _1 = function () {
    var count = 0;

    for (var i = 0; i < 100000000; i++) {
      count = count + 1;
    }

    return count;
  };

  _$0.loop_func = _1;
}).call(this);

JavaScriptの最適化(Closure Compiler)

Closure Compilerは公式から最適化を行います。

jsmod-clo.js

function loop_func_clo(){for(var a=0,b=0;1E8>b;b++)a+=1;return a};

最適化しましたが、両方ともあまり処理が短縮されていないように感じます。

実行

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

回数 JavaScript JavaScript prepack最適化あり JavaScript Closure Compiler最適化あり C++最適化なし C++最適化01 C++最適化02 C++最適化03
1 84.914 82.774 82.875 426.709 0.0699 0.0949 0.0499
2 83.609 82.669 82.170 421.660 0.0449 0.0500 0.0550
3 54.994 55.779 55.080 422.435 0.0499 0.0350 0.0499
4 54.495 57.089 55.375 429.959 0.0450 0.2099 0.0599
5 54.455 55.075 55.044 421.464 0.0299 0.0399 0.0500

結果を見ると、予想通りJavaScriptの最適化はあまり早くなっていないことがわかりました。
それ以外は、前回と同様にC++の方が早く、C++は最適化を強くするたびに処理速度が速くなっています。
JavaScriptの最適化はC++の最適化とは異なり、抽象化の解消や関数の事前実行など若干使い方が異なるので早くならなかったのかなと思います。

一応以下を最適化すれば処理時間が短くなりそうなのですが、最適化がいつまでたっても終わらないので諦めました。

jsmod2.js

function loop_func_ori(){
    var count = 0;
    for(var i = 0; i < 100000000; i++){
        count = count + 1;
    }
    return count;
}

var i = loop_func_ori()

引数のある関数の比較

C++の引数のある関数をJavaScriptから呼び出した時の速度を試してみました。

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

引数のあるC++関数をJavaScriptから呼び出す方法は前に書いた独自のC ++関数の連携に引数を追加するだけです。

cmain.cpp

#include <emscripten/emscripten.h>

extern "C" {
    int EMSCRIPTEN_KEEPALIVE loop_func(int n) {
        int count = 0;
        while(true){ 
            count = count + 1;
            if (n == 0){
                break;
            }
            n = n - 1;
        }
        return count;
    }
}

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

htmlはほぼ同じなので省略します。

JavaScriptの作成と最適化

基本は先ほどと同じです。今回は関数の処理が変わるため、そこだけ変更しています。
各関数に与えている整数は1000000000にしています。

jsmod.js

function loop_func_js_ori(n){
    var count = 0;
    while(true){
        count = count + 1
        if (n == 0){
          break;
        }
        n = n -1;
    }
    return count;
}

実行

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

回数 JavaScript JavaScript prepack最適化あり JavaScript Closure Compiler最適化あり C++最適化01 C++最適化02 C++最適化03
1 672.230 672.750 672.200 0.0600 0.0549 0.0999
2 669.505 675.559 674.194 0.0450 0.0499 0.0550
3 665.550 669.945 678.404 0.0550 0.0350 0.0450
4 670.449 678.560 698.879 0.0450 0.0550 0.0399
5 672.009 671.985 720.339 0.0550 0.0400 0.0350

結果を見ると単純なループと同じようにC++の方がJavaScriptより速いことがわかりました。これはループでもわかっていたため、単純にそうなんだーぐらいの感想でした。

おわりに

コメントを受けて他のパターンを試してみました。意外!と思えなかったので、個人的にはあまり面白くない結果でした。今回は、JavaScriptの最適化に不利な処理での比較になってしまったためJavaScriptの最適化はあまりすごくないイメージができてしまいましたが、大きなJavaScriptを最適化すれば意味のあるものになると思います。JavaScriptの最適化に有利な方法を思いついたら比較してみようと思います。実験の結果では前回と感想は変わりませんでした。