JavaScriptによる大規模配列初期化のパフォーマンス検証

# 今回の目的

以下の自分のシリーズ記事で素数判別アルゴリズムの善悪を議論しているのだが，JavaScript によるプログラム内で大規模な配列を使用しており，この配列アクセスのオーバーヘッドが無視できない状況になっているというか，如何にオーバーヘッドを減らせるか？が主題になりつつある。

- [Qiitaで自作の素因数分解プログラムを公開したら添削されて30倍速くなった件](https://qiita.com/TETSURO1999/items/ffc1cae68c16a5c26fde)
- [Qiitaで自作の素因数分解プログラムを公開したら添削されて30倍速くなった件(2)](https://qiita.com/TETSURO1999/items/28ca00f958628423d64f)
- [Qiitaで自作の素因数分解プログラムを公開したら添削されて30倍速くなった件(3)](https://qiita.com/TETSURO1999/items/38ddcc0f4f3411924fe9)

自分はほぼ C 言語インタプリタのような感覚で JavaScript を利用してきたので，言語仕様的にもパフォーマンス的に曖昧な点を残したままだった。なので，今回の記事では JavaScript による大規模配列の性能テストを行ってみたい。

# 結論を先に書く
- もう無印の JScript からは卒業しろ！遅すぎる！
- JScript9 と Chakra では Chakra のほうが良いが，得手不得手があるので要注意。
- 大規模な配列を宣言するとき
  - 可能であれば `TypedArray` を使え！　※初期値としてゼロが保証されているのがいい
  - 配列の宣言は `new Array(size)` を使え！`new` を省略した `Array(size)`は使うな！
    ※`new` を付けたほうが速い
  -  `[]` で宣言して `push` は言うほど速くない。※可変サイズなので便利なんだけどね
- 大規模な配列を初期化するとき
  - 配列の初期化は `for(...)` ループで回せ！
  - 配列の初期化に `fill()` メソッドを使うな！
  - 配列にはできるだけ整数を代入しろ！
    スピードは速い順に `integer` ≧ `double` ＞ `bool` である。
    とくに Chakra は `bool` が遅いので要注意。

:::note
過去に自分の記事に掲載したサンプルコードは全て `new` が抜けている件
一応，脚注[^脚注1]によると，

>標準ビルトインオブジェクトの一部は `new` 演算子の省略が認められています。ただし，`new` 演算子を省略した場合に挙動が変わるものがあるので注意が必要です。

とあるので文法的には間違いではないらしい。
:::

[^脚注1]:[JavaScriptの配列初期化 - Qiita](https://qiita.com/yuta-ike/items/f60b940540a89332df2a)

# 宣言および初期化テスト結果
JavaScript で取り扱える安全な整数の上限 $2^{53} - 1$ の平方根を上回らない最大の整数 $94,906,265$ を配列の `size` として宣言および全域初期化するまでの時間を計測した。なお，Windows Script Host のデフォルトである JScript エンジン，Internet Explorer 9 以降の JScript9 エンジン，Chakra エンジンを比較してみたが，JScript エンジンだけは十数分経っても終了しなかったため，結果からは省いている。

<font color=red><b>有意に速い！というところは赤色で示した。</b></font>

<table>
<caption>表１　配列の宣言と初期化方法による処理時間の比較（単位は秒）</caption>
<thead>
<tr><th style="text-align:center;">No</th>
    <th style="text-align:center;">Declaration</th>
    <th style="text-align:center;">Initialize</th>
    <th style="text-align:center;">JScript9</th>
    <th style="text-align:center;">Chakra</th></tr>
</thead>
<tbody>
<tr><td style="text-align:center;">#000</td>
    <td rowspan=5>a = []</tdh>
    <td>for( ... ) a.push(true)</td>
    <td style="text-align:center;">1.315</td>
    <td style="text-align:center;">2.218</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#001</td>
    <td>for( ... ) a.push(0)</td>
    <td style="text-align:center;">2.242</td>
    <td style="text-align:center;">1.027</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#002</td>
    <td>for( ... ) a.push(0.0)</td>
    <td style="text-align:center;">2.252</td>
    <td style="text-align:center;">1.032</td></tr>
<tr><td style="text-align:center;">#100</td>
    <td rowspan=11>a = Array(size)</td>
    <td>for( ... ) a[i] = true</td>
    <td style="text-align:center;">1.426</td>
    <td style="text-align:center;">2.151</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#101</td>
    <td>for( ... ) a.push(0)</td>
    <td style="text-align:center;">1.452</td>
    <td style="text-align:center;">1.884</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#102</td>
    <td>for(...) a.push(0.0)</td>
    <td style="text-align:center;">1.360</td>
    <td style="text-align:center;">2.001</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#110</td>
    <td>a.fill(true)</td>
    <td style="text-align:center;">N/A</td>
    <td style="text-align:center;">2.629</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#111</td>
    <td>a.fill(0)</td>
    <td style="text-align:center;">N/A</td>
    <td style="text-align:center;">2.176</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#112</td>
    <td>a.fill(0.0)</td>
    <td style="text-align:center;">N/A</td>
    <td style="text-align:center;">2.168</td></tr>
<tr><td style="text-align:center;">#200</td>
    <td rowspan=11>a = new Array(size)</td>
    <td>for( ... ) a[i] = true</td>
    <td style="text-align:center;">1.380</td>
    <td style="text-align:center;">2.230</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#201</td>
    <td>for( ... ) a[i] = 0</td>
    <td style="text-align:center;"><font color=red><b>0.955</b></font></td>
    <td style="text-align:center;"><font color=red><b>0.584</b></font></td></tr>
<tr></tr>
<tr><td style="text-align:center;">#202</td>
    <td>for( ... ) a[i] = 0.0</td>
    <td style="text-align:center;"><font color=red><b>0.963</b></font></td>
    <td style="text-align:center;"><font color=red><b>0.597</b></font></td></tr>
<tr></tr>
<tr><td style="text-align:center;">#210</td>
    <td>a.fill(true)</td>
    <td style="text-align:center;">N/A</td>
    <td style="text-align:center;">2.597</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#211</td>
    <td>a.fill(0)</td>
    <td style="text-align:center;">N/A</td>
    <td style="text-align:center;">1.142</td></tr>
<tr></tr>
<tr><td style="text-align:center;">#212</td>
    <td>a.fill(0.0)</td>
    <td style="text-align:center;">N/A</td>
    <td style="text-align:center;">1.156</td></tr>
<tr><td style="text-align:center;">#300</td>
    <td colspan=2>a = new Uint8Array(size)</td>
    <td style="text-align:center;">N/A</td>
    <td style="text-align:center;"><font color=red><b>0.064</b></font></td></tr>
<tr><td style="text-align:center;">#301</td>
    <td colspan=2>a = new Uint32Array(size)</td>
    <td style="text-align:center;">N/A</td>
    <td style="text-align:center;"><font color=red><b>0.154</b></font></td></tr>
<tr><td style="text-align:center;">#302</td>
    <td colspan=2>a = new Float64Array(size)</td>
    <td style="text-align:center;">N/A</td>
    <td style="text-align:center;"><font color=red><b>0.274</b></font></td></tr>
</tbody>
</table>

- Core i5 10400, 8GB, Windows10 22H2
- 計測時間は 10 回の平均値です。
- N/A（Not Available）利用不可

<details><summary>テストスクリプトはコチラ</summary>

```javascript:test-array.js
//------------------------------------------------------------------------------
// 定数定義
//------------------------------------------------------------------------------
var	MAX_SAFE_INTEGER = 9007199254740991;	// 最大の整数
var	MAX_SIEVE = 94906265;					// 最大の整数の平方根
//------------------------------------------------------------------------------
// テスト関数
//------------------------------------------------------------------------------
function test000() { var table = []; for( var i = 0; i < MAX_SIEVE; i++ ) table.push( true ); return( table ); }
function test001() { var table = []; for( var i = 0; i < MAX_SIEVE; i++ ) table.push( 0 );    return( table ); }
function test002() { var table = []; for( var i = 0; i < MAX_SIEVE; i++ ) table.push( 0.0 );  return( table ); }

function test100() { var table = Array( MAX_SIEVE ); for( var i = 0; i < table.length; i++ ) table[i] = true; return( table ); }
function test101() { var table = Array( MAX_SIEVE ); for( var i = 0; i < table.length; i++ ) table[i] = 0;    return( table ); }
function test102() { var table = Array( MAX_SIEVE ); for( var i = 0; i < table.length; i++ ) table[i] = 0.0;  return( table ); }
function test110() { var table = Array( MAX_SIEVE ); table.fill( true ); return( table ); }
function test111() { var table = Array( MAX_SIEVE ); table.fill( 0    ); return( table ); }
function test112() { var table = Array( MAX_SIEVE ); table.fill( 0.0  ); return( table ); }

function test200() { var table = new Array( MAX_SIEVE ); for( var i = 0; i < table.length; i++ ) table[i] = true; return( table ); }
function test201() { var table = new Array( MAX_SIEVE ); for( var i = 0; i < table.length; i++ ) table[i] = 0;    return( table ); }
function test202() { var table = new Array( MAX_SIEVE ); for( var i = 0; i < table.length; i++ ) table[i] = 0.0;  return( table ); }
function test210() { var table = new Array( MAX_SIEVE ); table.fill( true ); return( table ); }
function test211() { var table = new Array( MAX_SIEVE ); table.fill( 0    ); return( table ); }
function test212() { var table = new Array( MAX_SIEVE ); table.fill( 0.0  ); return( table ); }

function test300() { var table = new Uint8Array( MAX_SIEVE );   return( table ); }
function test301() { var table = new Uint32Array( MAX_SIEVE );  return( table ); }
function test302() { var table = new Float64Array( MAX_SIEVE ); return( table ); }
//------------------------------------------------------------------------------
// 関数テーブル
//------------------------------------------------------------------------------
var	func_table = {
	"000":test000, "001":test001, "002":test002,
	"100":test100, "101":test101, "102":test102,"110":test110,"111":test111,"112":test112,
	"200":test200, "201":test201, "202":test202,"210":test210,"211":test211,"212":test212,
	"300":test300, "301":test301, "302":test302
};
//------------------------------------------------------------------------------
// メイン関数
//------------------------------------------------------------------------------
function main( args ) {
	if( args.Count == 0 ) {
		WScript.Echo( "Usage: test-array(.js) [no]" );
		WScript.Echo( "no   declaration          initialize" );
		WScript.Echo( "000: a = []               for(...) a.push(true)" );
		WScript.Echo( "001: a = []               for(...) a.push(0)" );
		WScript.Echo( "002: a = []               for(...) a.push(0.0)" );
		WScript.Echo( "100: a = Array(size)      for(...) a[i] = true" );
		WScript.Echo( "101: a = Array(size)      for(...) a[i] = 0" );
		WScript.Echo( "102: a = Array(size)      for(...) a[i] = 0.0" );
		WScript.Echo( "110: a = Array(size)      a.fill(true)" );
		WScript.Echo( "111: a = Array(size)      a.fill(0)" );
		WScript.Echo( "112: a = Array(size)      a.fill(0.0)" );
		WScript.Echo( "200: a = new Array(size)  for(...) a[i] = true" );
		WScript.Echo( "201: a = new Array(size)  for(...) a[i] = 0" );
		WScript.Echo( "202: a = new Array(size)  for(...) a[i] = 0.0" );
		WScript.Echo( "210: a = new Array(size)  a.fill(true)" );
		WScript.Echo( "211: a = new Array(size)  a.fill(0)" );
		WScript.Echo( "212: a = new Array(size)  a.fill(0.0)" );
		WScript.Echo( "300: a = new Uint8Array(size)" );
		WScript.Echo( "301: a = new Uint32Array(size)" );
		WScript.Echo( "302: a = new Float64Array(size)" );
		return( false );
	}
	var	func = func_table[args(0)];
	if( func == null ) {
		WScript.Echo( "コマンド " + args(0) + " には未対応です!!"  );
		return( false );
	}
	var	table = func();
	return( true );
}
var	args = WScript.Arguments.Unnamed;
var	ret = main( args );
```

</details>

# 今後の課題

しかしながら，脚注[^脚注2]によると，Google 謹製の Chrome V8 エンジンでは逆に `new Array(size)` を使用せず，`[]` あるいは `new Array()` のように空配列で宣言して `push` で増やしていくのが良いとのこと。`Packed Array` と `Holey Array` の問題らしい。

そもそも最大与党の V8 エンジンを評価してないのってダメだよな・・・


[^脚注2]: [高速化の観点から new Array(100) を使わない方が良い理由](http://nmi.jp/2019-06-09-The-reason-you-should-avoid-new-array-100)