今回の目的
以下の自分のシリーズ記事で素数判別アルゴリズムの善悪を議論しているのだが,JavaScript によるプログラム内で大規模な配列を使用しており,この配列アクセスのオーバーヘッドが無視できない状況になっているというか,如何にオーバーヘッドを減らせるか?が主題になりつつある。
- Qiitaで自作の素因数分解プログラムを公開したら添削されて30倍速くなった件
- Qiitaで自作の素因数分解プログラムを公開したら添削されて30倍速くなった件(2)
- Qiitaで自作の素因数分解プログラムを公開したら添削されて30倍速くなった件(3)
自分はほぼ C 言語インタプリタのような感覚で JavaScript を利用してきたので,言語仕様的にもパフォーマンス的に曖昧な点を残したままだった。なので,今回の記事では JavaScript による大規模配列の性能テストを行ってみたい。
結論を先に書く
- もう無印の JScript からは卒業しろ!遅すぎる!
- JScript9 と Chakra では Chakra のほうが良いが,得手不得手があるので要注意。
- 大規模な配列を宣言するとき
- 可能であれば
TypedArray
を使え! ※初期値としてゼロが保証されているのがいい - 配列の宣言は
new Array(size)
を使え!new
を省略したArray(size)
は使うな!
※new
を付けたほうが速い -
[]
で宣言してpush
は言うほど速くない。※可変サイズなので便利なんだけどね
- 可能であれば
- 大規模な配列を初期化するとき
- 配列の初期化は
for(...)
ループで回せ! - 配列の初期化に
fill()
メソッドを使うな! - 配列にはできるだけ整数を代入しろ!
スピードは速い順にinteger
≧double
>bool
である。
とくに Chakra はbool
が遅いので要注意。
- 配列の初期化は
過去に自分の記事に掲載したサンプルコードは全て new
が抜けている件
一応,脚注1によると,
標準ビルトインオブジェクトの一部は
new
演算子の省略が認められています。ただし,new
演算子を省略した場合に挙動が変わるものがあるので注意が必要です。
とあるので文法的には間違いではないらしい。
宣言および初期化テスト結果
JavaScript で取り扱える安全な整数の上限 $2^{53} - 1$ の平方根を上回らない最大の整数 $94,906,265$ を配列の size
として宣言および全域初期化するまでの時間を計測した。なお,Windows Script Host のデフォルトである JScript エンジン,Internet Explorer 9 以降の JScript9 エンジン,Chakra エンジンを比較してみたが,JScript エンジンだけは十数分経っても終了しなかったため,結果からは省いている。
有意に速い!というところは赤色で示した。
No | Declaration | Initialize | JScript9 | Chakra |
---|---|---|---|---|
#000 | a = [] | for( ... ) a.push(true) | 1.315 | 2.218 |
#001 | for( ... ) a.push(0) | 2.242 | 1.027 | |
#002 | for( ... ) a.push(0.0) | 2.252 | 1.032 | |
#100 | a = Array(size) | for( ... ) a[i] = true | 1.426 | 2.151 |
#101 | for( ... ) a.push(0) | 1.452 | 1.884 | |
#102 | for(...) a.push(0.0) | 1.360 | 2.001 | |
#110 | a.fill(true) | N/A | 2.629 | |
#111 | a.fill(0) | N/A | 2.176 | |
#112 | a.fill(0.0) | N/A | 2.168 | |
#200 | a = new Array(size) | for( ... ) a[i] = true | 1.380 | 2.230 |
#201 | for( ... ) a[i] = 0 | 0.955 | 0.584 | |
#202 | for( ... ) a[i] = 0.0 | 0.963 | 0.597 | |
#210 | a.fill(true) | N/A | 2.597 | |
#211 | a.fill(0) | N/A | 1.142 | |
#212 | a.fill(0.0) | N/A | 1.156 | |
#300 | a = new Uint8Array(size) | N/A | 0.064 | |
#301 | a = new Uint32Array(size) | N/A | 0.154 | |
#302 | a = new Float64Array(size) | N/A | 0.274 |
- Core i5 10400, 8GB, Windows10 22H2
- 計測時間は 10 回の平均値です。
- N/A(Not Available)利用不可
テストスクリプトはコチラ
//------------------------------------------------------------------------------
// 定数定義
//------------------------------------------------------------------------------
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 );
今後の課題
しかしながら,脚注2によると,Google 謹製の Chrome V8 エンジンでは逆に new Array(size)
を使用せず,[]
あるいは new Array()
のように空配列で宣言して push
で増やしていくのが良いとのこと。Packed Array
と Holey Array
の問題らしい。
そもそも最大与党の V8 エンジンを評価してないのってダメだよな・・・