JavaScript(V8)で避けるべき(だった?)クロージャの使い方
Grokking V8 closures for fun (and profit?) に、ほんの少しだけ触れられている話なんですが。
ごく最近まで V8 には、オブジェクトリテラルの中で関数リテラルを使った場合に非常に遅くなる(というかGCが多発する)問題があった。
たとえば、
function doit() { for (var i = 0; i < 1000; ++i) { for (var j = 0; j < 1000; ++j) { var o = { f: function () { return i + j; } }; } } } doit();
というコードを node-0.6.19 で実行すると、以下のように mark-sweep GC が大量に発生して処理に時間がかかっていることが分かる。
$ time /usr/local/node-0.6.19/bin/node --trace-gc ~/tmp/closure-leak.js Scavenge 2.3 -> 1.9 MB, 0 ms. Scavenge 2.7 -> 2.0 MB, 0 ms. Mark-sweep 3.1 -> 1.7 MB, 1 ms. Scavenge 4.0 -> 2.2 MB, 0 ms. (中略) Mark-sweep 11.7 -> 1.6 MB, 3 ms. Mark-sweep 19.8 -> 1.6 MB, 4 ms. Mark-sweep 19.8 -> 1.6 MB, 5 ms. Mark-sweep 19.8 -> 1.6 MB, 4 ms. Mark-sweep 19.8 -> 1.6 MB, 4 ms. Mark-sweep 19.8 -> 1.6 MB, 4 ms. real 0m0.892s user 0m0.871s sys 0m0.026s
ちなみに最新の V8 では、このコードを実行するのに 30ms 程度しかかかりません。30倍の高速化www
最新版で修正されているとは言え、ブラウザがアップデートされない Android では、JavaScript コード側での対応が引き続き必要な案件のひとつですね。以下のように書くことで問題を回避しましょう。
function doit() { for (var i = 0; i < 1000; ++i) { for (var j = 0; j < 1000; ++j) { var f = function () { return i + j; }; var o = { f: f }; } } } doit();
20:54追記: free variable の有無に関係ない話なので、例をかえました