昨日twitterで日中に流れていた話題。
インクリメントは前置きの方がパフォーマンスが良くなるというアレをjavascriptの世界で議論していた。
話題としては、「前置きの方が早くなるよね」「でもJITで最適化が効きそうだよね」としつつ、前置きが無難。という結論だったかんじ。
夜になるとvolatileの話題のほうが盛り上がって忘れ去られていって残念だったけど
JavaScriptでインクリメントは変数の前、後ろどちらの方が高速?
こちらでベンチマークが紹介されていました。
実測すると・・・
となり、意外や意外。
Chromeでは後置きの方が圧倒的に早いという結果になった!
(*原因は後述)
前置きが他のブラウザより酷く遅いというわけではなく、
後置きがめちゃくちゃ早い!
ここまで差が出るのは
javascriptエンジンがどんな最適化をしている影響か気になるよね。
v8の生成する実行コードを見たくなった。
それにあたってまずエンジン単体での
スクリプトの実行時間(とコンパイル時間)を計る。
ここを参考にlibv8を導入して、
Hello,World!を少しいじってclock()関数で計測。
#include <iostream> #include <v8.h> static const std::string preparation_code = "var data = new Array(500 * 1000);var index = data.length - 1;while (index--) data[index] = index;"; static const std::string prefix_increment = "for (var index = 0, len = data.length; index < len; ++index) {data[index] = data[index] * 2;}"; static const std::string suffix_increment = "for (var index = 0, len = data.length; index < len; index++) {data[index] = data[index] * 2;}"; void run_preparation(void); void run_prefix(void); void run_suffix(void); void run_script(const std::string &test_name, const std::string &js); int main (int argc, const char * argv[]) { run_prefix(); run_suffix(); return 0; } void run_preparation() { v8::HandleScope handle_scope; //preparation v8::Handle<v8::String> source = v8::String::New(preparation_code.c_str()); v8::Handle<v8::Script> script = v8::Script::Compile(source); v8::Handle<v8::Value> result = script->Run(); } void run_prefix() { run_script("prefix-test", prefix_increment); } void run_suffix() { run_script("suffix-test", suffix_increment); } void run_script(const std::string &test_name, const std::string &js) { clock_t start, end; v8::HandleScope handle_scope; v8::Persistent<v8::Context> context = v8::Context::New(); context = v8::Context::New(); v8::Context::Scope context_scope(context); //preparation run_preparation(); //increment v8::Handle<v8::String> source = v8::String::New(js.c_str()); start = clock(); v8::Handle<v8::Script> script = v8::Script::Compile(source); end = clock(); std::cout << test_name << " : compile = " << end - start << std::endl; start = clock(); v8::Handle<v8::Value> result = script->Run(); end = clock(); std::cout << test_name << " : execute = " << end - start << std::endl; //dispose context.Dispose(); }
結果
環境はMacBookAir(i7) / v8(リビジョン: 10833)
500*1000を100回試行して平均をとる。
途中、後置きと前置きの実行順を入れ替える。
prefix-test : compile = 95 prefix-test : execute = 11551 suffix-test : compile = 93 suffix-test : execute = 12158
あれぇ?だいたいこんな感じで有意差は微妙という結果になった。
前置きの方が微妙に早いかな?
ちなみにindex=index+1も試したけどこちらも有意差は無かったです。
ちなみに以下の傾向があった
1)
前置き、後ろ置き共に
先に実行した場合は後で実行する場合より10%程度遅くなる。
(別のコンテキスト使っているのに・・・メモリブロック確保とかしているのかな?)
2)
コンテキストを継続して
初期化ー>コンパイルー>実行ー>実行・・・
の形で実行を複数回行うと2回目以降の実行時間が倍くらいになった。
ここで、Chrome側のJITのやり方に原因があるのか、
Chromeのv8って仕様が違うバージョン?と悩んだところで。
ベンチの実装により差が出ているぽい
らしいです。
JSでi++と++iどっちが速い?
ええ、ベンチマークがどんな挙動をしているのか
調べておくのは基本ですね:p
それでも、このケースでChromeだけ後置きが早くなる理由は気になるところです。