きっかけ
前回の記事を書くのために XSS の攻撃方法をいろいろ実験していたんだけど、同じ攻撃でもいろいろなスクリプトの挿入方法があることが分かったので、実行速度に差がないのか調べていたんですね。
それについては後日書くつもりなんですけど、JavaScript のパフォーマンスについてググっていたら、配列に 1000 個の要素を追加するときに [ ] で宣言する場合と、new Array(1000) で宣言する場合、[ ] のほうが V8 では2倍ほど速かったという記事を見つけてびっくりしました(゚д゚)
どういう実装にしたらそんなことが起こるんだろう?
C++ の std::vector だと、内部でバッファサイズが足りなくなると2倍の連続したメモリーを確保して、そちらへ丸ごとコピーする実装だったよね。
.NET Framework の List<T> クラスも同じような実装だったはず・・・。
private void EnsureCapacity(int min) {
if (_items.Length < min) {
int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
}
}
やっぱり2倍に拡張してるよね。
そしたらサイズが大きくなるに従って連続したメモリー確保とコピーコストは跳ね上がっていくから、最初からサイズを渡せるならその方がいいはず。
.NET のメモリー管理をちゃんと勉強してないけど、マネージだとメモリー連続してなくてもよかったりするのかな?
JavaScript だとメモリー連続してなくてもよさそうだから、1つのバッファが極端に大きくならないように一定程度大きくなると別のバッファを確保して連続しているように見せるのかなぁ・・・。
どういう実装なのか想像がつかないわ(´・ω・`)
実験してみた
というわけで、本当なのか実験してみました。
function test() {
var run = 100;
var cnt = 10000;
var size = 10000000 / cnt;
var msA = 0;
var msB = 0;
for (var i = 0; i < run; i++) {
msA += testA(cnt, size);
msB += testB(cnt, size);
}
console.log('A ' + (msA / run) + " ms");
console.log('B ' + (msB / run) + " ms");
function testA() {
var startTime = new Date();
for (var i = 0; i < cnt; i++) {
var arr = [];
for (var j = 0; j < size; j++) {
arr[j] = 0;
}
}
var endTime = new Date();
return endTime - startTime;
}
function testB() {
var startTime = new Date();
for (var i = 0; i < cnt; i++) {
var arr = new Array(size)
for (var j = 0; j < size; j++) {
arr[j] = 0;
}
}
var endTime = new Date();
return endTime - startTime;
}
}
配列にゼロを1000万回代入して、それに掛かる時間を計測します。
それを100回実行した平均値を調べてみました。
cnt
変数の数字を大きくすると、小さな配列を何度も作って代入し、cnt
変数の数字を小さくすると、大きな配列を少ない回数作って代入するので、以下のような関係になります。
実行回数を合わせると時間掛かりすぎるので,こういう調べ方にしました(;´д`)
|配列サイズ|10|100|1,000|10,000|100,000|1,000,000|
|---|---|---|---|---|---|---|---|
|実行回数|1,000,000|100,000|10,000|1,000|100|10|
計測結果
基本的に new Array(x) のほうが速いですね。
V8 の処理の仕方がかわったのかな?
Chrome と Opera は傾向が完全に一致していて、誤差レベルだけど Opera のほうがちょっとだけ速い。
でも配列サイズが10万になったとたんに、Chrome と Opera はパフォーマンスが極端に落ちていて、new Array(x) の方は IE と同レベルになっちゃいました。
よく使われるサイズで高速になるようにチューニングしたっぽいですね。
配列 10 個の場合
Internet Explorer 11 |
Google Chrome 37 |
Mozilla Firefox 32 |
Opera 24 | |
---|---|---|---|---|
[ ] | 656.67 | 42.80 | 78.98 | 41.29 |
new Array(x) | 737.88 | 22.69 | 126.77 | 21.36 |
配列 100 個の場合
Internet Explorer 11 |
Google Chrome 37 |
Mozilla Firefox 32 |
Opera 24 | |
---|---|---|---|---|
[ ] | 633.71 | 48.32 | 89.79 | 45.60 |
new Array(x) | 576.24 | 14.10 | 35.15 | 12.97 |
配列 1,000 個の場合
Internet Explorer 11 |
Google Chrome 37 |
Mozilla Firefox 32 |
Opera 24 | |
---|---|---|---|---|
[ ] | 612.07 | 41.73 | 117.06 | 38.99 |
new Array(x) | 559.40 | 10.64 | 54.06 | 9.82 |
配列 10,000 個の場合
Internet Explorer 11 |
Google Chrome 37 |
Mozilla Firefox 32 |
Opera 24 | |
---|---|---|---|---|
[ ] | 619.40 | 43.41 | 76.83 | 41.22 |
new Array(x) | 558.55 | 10.39 | 80.28 | 9.88 |
配列 100,000 個の場合
Internet Explorer 11 |
Google Chrome 37 |
Mozilla Firefox 32 |
Opera 24 | |
---|---|---|---|---|
[ ] | 625.12 | 42.16 | 70.14 | 39.25 |
new Array(x) | 559.32 | 411.66 | 55.29 | 375.22 |
配列 1,000,000 個の場合
Internet Explorer 11 |
Google Chrome 37 |
Mozilla Firefox 32 |
Opera 24 | |
---|---|---|---|---|
[ ] | 646.35 | 153.36 | 67.02 | 132.58 |
new Array(x) | 598.95 | 807.45 | 66.91 | 782.40 |
結論:お好きな方をどうぞ
どのブラウザでも配列のサイズが100~1000個のときが最速で、10個まで小さいと微妙にパフォーマンスが落ちています。
[ ] のほうが速かったケースは 10個のときの IE と Firefox、10万個以上のときの Chrome と Opera くらいかな。
配列を何度も作成する特殊なケースがあるなら、10000個以下なら new Array(x) がよさそうで、それ以上の個数なら [ ] がいい。
でも普通に配列をいくつか作って使う程度なら、ぶっちゃけどっちでも変わらないから好きな方を使ったらいいんじゃないかなぁ。
可読性とか、タイプ数とか、検索性とか、コード規約とか、置かれた状況と 個人的な好み で選択すればいいと思います (´∀`)