web-dev-qa-db-ja.com

JavascriptTypedArrayのパフォーマンス

TypedArraysが通常の配列より速くないのはなぜですか? CLZ(先行ゼロの計算関数)にprecalc値を使用したい。そして、私は彼らが通常のオブジェクトとして解釈したくないのですか?

http://jsperf.com/array-access-speed-2/2

準備コード:

 Benchmark.prototype.setup = function() {
   var buffer = new ArrayBuffer(0x10000);
   var Uint32 = new Uint32Array(buffer);
   var arr = [];
   for(var i = 0; i < 0x10000; ++i) {
     Uint32[i] = (Math.random() * 0x100000000) | 0;
     arr[i] = Uint32[i];
   }
   var sum = 0;
 };

テスト1:

sum = arr[(Math.random() * 0x10000) | 0];

テスト2:

sum = Uint32[(Math.random() * 0x10000) | 0];

enter image description here

PS私のパフォーマンステストは無効である可能性があります。自由に修正してください。

20
var buffer = new ArrayBuffer(0x10000);
var Uint32 = new Uint32Array(buffer);

と同じものではありません:

var Uint32 = new Uint32Array(0x10000);

新しいArrayBuffer(常に配列バッファーを取得します。どちらの場合もUint32.bufferを参照)のためではなく、長さパラメーターのためです。ArrayBufferでは要素ごとに1バイト、Uint32Arrayでは要素ごとに4バイトです。

したがって、最初のケース(およびコード内)では、Uint32.length = 0x1000/4であり、ループは4回のうち3回範囲外です。しかし、残念ながら、エラーが発生することはなく、パフォーマンスが低下するだけです。

'new ArrayBuffer'を使用して、次のようにUint32を宣言する必要があります。

var buffer = new ArrayBuffer(0x10000 * 4);
var Uint32 = new Uint32Array(buffer);

jsperf with(0x10000) および jsperf with(0x10000 * 4) を参照してください。

34
radsoc

最新のエンジンは、Arrayを使用できると考えても、舞台裏で真の配列を使用します。真の配列を使用できないと思わせるようなことを行うと、プロパティマップの「配列」にフォールバックします。

また、 radsocが指摘var buffer = new ArrayBuffer(0x10000) then var Uint32 = new Uint32Array(buffer)は、サイズが0x10000ではなく0x4000(0x10000/4)のUint32配列を生成することにも注意してください。 ArrayBufferはバイト単位ですが、もちろんUint32Arrayエントリごとに4バイトあります。以下のすべては、代わりにnew Uint32Array(0x10000)を使用します(そして、この編集の前でも常に使用しました)リンゴとリンゴを比較します。

では、new Uint32Array(0x10000)から始めましょう。 http://jsperf.com/array-access-speed-2/11(残念ながら、JSPerfはこのテストとその結果を失い、現在は完全にオフラインになっています)

graph showing roughly equivalent performance

これは、単純で予測可能な方法で配列を埋めているため、最新のエンジンは、シフトオーバーするのではなく、カバーの下で真の配列(そのパフォーマンス上の利点)を引き続き使用することを示唆しています。どちらも基本的に同じパフォーマンスです。速度の違いは、Uint32値を取得してそれをsumとしてnumberに割り当てる型変換に関連している可能性があります(ただし、その型変換が延期されていない場合は驚きます。 ..)。

ただし、いくつかの混乱を追加します。

var Uint32 = new Uint32Array(0x10000);
var arr = [];
for (var i = 0x10000 - 1; i >= 0; --i) {
  Uint32[Math.random() * 0x10000 | 0] = (Math.random() * 0x100000000) | 0;
  arr[Math.random() * 0x10000 | 0] = (Math.random() * 0x100000000) | 0;
}
var sum = 0;

...エンジンが昔ながらのプロパティマップ「配列」にフォールバックする必要があるため、型指定された配列は昔ながらの種類よりも著しく優れていることがわかります。 http://jsperf.com/array-access-speed-2/(残念ながら、JSPerfはこのテストとその結果を失いました)

bar graph showing marked performance improvement for typed arrays

賢い、これらのJavaScriptエンジンエンジニア...

ただし、Array配列の非配列の性質で行う特定のことは重要です。考慮してください:

var Uint32 = new Uint32Array(0x10000);
var arr = [];
arr.foo = "bar";                            // <== Non-element property
for (var i = 0; i < 0x10000; ++i) {
  Uint32[i] = (Math.random() * 0x100000000) | 0;
  arr[i] = (Math.random() * 0x100000000) | 0;
}
var sum = 0;

それでも予想どおりに配列がいっぱいになりますが、要素以外のプロパティ(foo)を追加します。 http://jsperf.com/array-access-speed-2/4(残念ながら、JSPerfはこのテストとその結果を失いました)どうやら、エンジンは非常に巧妙であり、続行している間、その非要素プロパティを脇に置いておきます要素のプロパティに真の配列を使用するには:

bar graph showing performance improvement for standard arrays when <code>Array</code> array gets non-element property

上記の最初のテストと比較して、標準配列が高速になる理由を説明するのに少し戸惑っています。測定誤差? Math.randomの気まぐれ?しかし、Arrayの配列固有のデータがまだ真の配列であると確信しています。

一方、同じことを行って、逆の順序で入力した場合:

var Uint32 = new Uint32Array(0x10000);
var arr = [];
arr.foo = "bar";                            // <== Non-element property
for (var i = 0x10000 - 1; i >= 0; --i) {    // <== Reverse order
  Uint32[i] = (Math.random() * 0x100000000) | 0;
  arr[i] = (Math.random() * 0x100000000) | 0;
}
var sum = 0;

... IE11を除いて、型付き配列の勝利に戻ります。 http://jsperf.com/array-access-speed-2/9(残念ながら、JSPerfはこのテストとその結果を失いました)

graph showing typed arrays winning except on IE11

35
T.J. Crowder

あなたの場合、パフォーマンスが悪い理由は、配列の長さのバグのために、Uint32Arrayを使用しているときに配列の外側を読み取ろうとすることです。

しかし、それが本当の理由ではない場合は、次のようにします。

Uint32Arrayの代わりにInt32Arrayを使用してみてください。 V8では、変数にuint32型を含めることはできませんが、int32/double /ポインタを含めることはできます。したがって、uint32タイプを変数に割り当てると、より遅いdoubleに変換されます。

32ビットバージョンのV8を使用している場合、変数はint31/double /ポインター型を持つことができます。したがって、int32もdoubleに変換されます。ただし、通常の配列を使用し、すべての値がint31の場合、変換は必要ないため、通常の配列の方が高速です。

また、int16を使用すると、int32を取得するためにいくつかの変換が必要になる場合があります(符号と1の補数のため)。 V8は左側にゼロを追加できるため、Uint16は変換を必要としません。

PS。ポインタに興味があるかもしれませんが、int31(またはx64ではint32)はV8と同じです。これは、int32がx64で8バイトを必要とすることも意味します。また、これがx86にint32型がない理由です。整数を格納するために32ビットすべてを使用すると、ポインターを保存するためのスペースがなくなるためです。

0
vitaliydev