昨日twitterで日中に流れていた話題。

インクリメントは前置きの方がパフォーマンスが良くなるというアレをjavascriptの世界で議論していた。
話題としては、「前置きの方が早くなるよね」「でもJITで最適化が効きそうだよね」としつつ、前置きが無難。という結論だったかんじ。


夜になるとvolatileの話題のほうが盛り上がって忘れ去られていって残念だったけど

JavaScriptでインクリメントは変数の前、後ろどちらの方が高速?
こちらでベンチマークが紹介されていました。

実測すると・・・

Chrome

Safari

iPhone


となり、意外や意外。
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だけ後置きが早くなる理由は気になるところです。