28

私はJavaScriptで型付き配列をいじっています。

var buffer = new ArrayBuffer(16);
var int32View = new Int32Array(buffer);

通常の配列([1, 257, true])JavaScriptでは、値が任意のタイプである可能性があるため、パフォーマンスが低下します。したがって、メモリ内のオフセットに到達することは簡単ではありません。

私は当初、JavaScript配列の添え字はオブジェクトと同じように機能し(多くの類似点があるため)、ハッシュマップベースであり、ハッシュベースのルックアップが必要だと考えていました。しかし、これを確認するための信頼できる情報はあまり見つかりませんでした。

したがって、型付き配列が非常にうまく機能する理由は、常に型付きであるCの通常の配列のように機能するためだと思います。上記の最初のコード例を考えて、型付き配列の10番目の値を取得したい...

var value = int32View[10];
  • タイプはInt32、であるため、各値は32ビットまたは4バイトで構成されている必要があります。
  • 下付き文字は10です。
  • したがって、その値のメモリ内の場所はであり、バイトを<array offset> + (4 * 10)読み取って合計値を取得します。4

基本的には自分の仮定を確認したいだけです。これについての私の考えは正しいですか、そうでない場合は、詳しく説明してください。

V8ソースをチェックして、自分で答えられるかどうかを確認しましたが、Cが錆びていて、C++にあまり詳しくありません。

4

4 に答える 4

50

型付き配列は、パフォーマンス上の理由から、WebGL標準化委員会によって設計されました。通常、Javascript配列は汎用であり、オブジェクトや他の配列などを保持できます。要素は、Cの場合のように、必ずしもメモリ内でシーケンシャルである必要はありません。WebGLでは、基になるC APIが期待する方法であるため、バッファがメモリ内でシーケンシャルである必要があります。彼ら。型付き配列を使用しない場合、通常の配列をWebGL関数に渡すには、多くの作業が必要です。各要素を検査し、型をチェックし、それが正しい場合(floatなど)、別のシーケンシャルにコピーする必要があります。 Cのようなバッファ、次にそのシーケンシャルバッファをCAPIに渡します。痛い-たくさんの仕事!パフォーマンスに敏感なWebGLアプリケーションの場合、これによりフレームレートが大幅に低下する可能性があります。

一方、質問で示唆しているように、型付き配列は、舞台裏のストレージにすでにあるシーケンシャルCのようなバッファーを使用します。型付き配列に書き込む場合、実際には、舞台裏でCのような配列に割り当てています。WebGLの目的では、これは、対応するCAPIがバッファーを直接使用できることを意味します。

メモリアドレスの計算だけでは不十分であることに注意してください。範囲外のアクセスを防ぐために、ブラウザも配列を境界チェックする必要があります。これはあらゆる種類のJavascript配列で発生する必要がありますが、多くの場合、巧妙なJavascriptエンジンは、インデックス値がすでに範囲内にあることを証明できる場合(0から配列の長さへのループなど)にチェックを省略できます。また、配列インデックスが実際には数値であり、文字列などではないことを確認する必要があります。しかし、本質的には、Cのようなアドレス指定を使用して説明するのと同じです。

しかし...それだけではありません!場合によっては、巧妙なJavascriptエンジンで、通常のJavascript配列のタイプを推測することもできます。V8のようなエンジンでは、通常のJavascript配列を作成し、それにfloatのみを格納すると、V8はそれがfloatの配列であると楽観的に判断し、そのために生成するコードを最適化する場合があります。その場合、パフォーマンスは型付き配列と同等になります。したがって、最大のパフォーマンスを達成するために、型付き配列は実際には必要ありません。配列を予測どおりに使用するだけで(すべての要素が同じ型で)、一部のエンジンもそのために最適化できます。

では、なぜ型付き配列がまだ存在する必要があるのでしょうか。

  • 配列のタイプを推測するような最適化は本当に複雑です。V8が、通常の配列にfloatのみが含まれていると推測した場合、オブジェクトを要素に格納します。最適化を解除して、配列を再びジェネリックにするコードを再生成する必要があります。これらすべてが透過的に機能することは、かなりの成果です。型付き配列ははるかに単純です。1つの型であることが保証されており、オブジェクトなどの他のものを格納することはできません。
  • 最適化が行われることが保証されることはありません。通常の配列にはfloatのみを格納できますが、エンジンはさまざまな理由でそれを最適化しないことを決定する場合があります。
  • それらがはるかに単純であるという事実は、他のそれほど洗練されていないjavascriptエンジンがそれらを簡単に実装できることを意味します。高度な最適化解除のサポートはすべて必要ありません。
  • 非常に高度なエンジンを使用しても、最適化を使用できることを証明することは非常に困難であり、不可能な場合もあります。型付き配列は、エンジンがその周りで最適化できる必要がある証明のレベルを大幅に簡素化します。型付き配列から返される値は確かに特定の型であり、エンジンはその型の結果を最適化できます。通常の配列から返される値は理論的には任意の型である可能性があり、エンジンは常に同じ型の結果になることを証明できない可能性があるため、効率の低いコードが生成されます。したがって、型付き配列の周りのコードはより簡単に最適化されます。
  • 型付き配列は、間違いを犯す機会を取り除きます。誤ってオブジェクトを保存して、突然パフォーマンスが大幅に低下することはありません。

したがって、要するに、通常の配列は、理論的には型付き配列と同じくらい高速である可能性があります。ただし、型付き配列を使用すると、ピークパフォーマンスに到達するのがはるかに簡単になります。

于 2012-11-11T20:06:06.647 に答える
7

はい、あなたはほとんど正しいです。標準のJavaScript配列では、JavaScriptエンジンは、配列内のデータがすべてオブジェクトであると想定する必要があります。これは、Cのような配列/ベクトルとして保存できます。ここで、メモリへのアクセスは、説明したとおりです。問題は、データが値ではなく、その値(オブジェクト)を参照するものであるということです。

したがって、実行a[i] = b[i] + 2するには、エンジンが次のことを行う必要があります。

  1. インデックスiでbのオブジェクトにアクセスします。
  2. オブジェクトのタイプを確認してください。
  3. オブジェクトから値を抽出します。
  4. 値に2を追加します。
  5. 4から新しく計算された値で新しいオブジェクトを作成します。
  6. 手順5の新しいオブジェクトをインデックスiに割り当てます。

型付き配列を使用すると、エンジンは次のことができます。

  1. インデックスiのbの値にアクセスします(CPUレジスタへの配置を含む)。
  2. 値を2インクリメントします。
  3. 手順2の新しいオブジェクトをインデックスiに割り当てます。

注:これらは、コンパイルされるコード(周囲のコードを含む)と問題のエンジンに依存するため、JavaScriptエンジンが実行する正確な手順ではありません。

これにより、結果の計算がはるかに効率的になります。また、型指定された配列にはメモリレイアウト保証(nバイト値の配列)があるため、データ(オーディオ、ビデオなど)と直接インターフェイスするために使用できます。

于 2012-11-11T19:17:52.233 に答える
3

パフォーマンスに関しては、物事は急速に変化する可能性があります。AshleysBrainが言うように、VMが、通常の配列を型付き配列として迅速かつ正確に実装できると推測できるかどうかにかかっています。これは、特定のJavaScript VMの特定の最適化に依存し、新しいブラウザバージョンで変更される可能性があります。

このChrome開発者のコ​​メントは、2012年6月の時点で機能したガイダンスを提供します。

  1. 多くのシーケンシャルアクセスを行う場合、通常の配列は型付き配列と同じくらい高速になります。アレイの境界外にランダムアクセスすると、アレイが大きくなります。
  2. 型付き配列はアクセスが高速ですが、割り当てが遅くなります。一時配列を頻繁に作成する場合は、型付き配列を避けてください。(これを修正することは可能ですが、優先度は低くなります。)
  3. JSPerfなどのマイクロベンチマークは、実際のパフォーマンスには信頼できません。

最後の点を詳しく説明すると、Javaでこの現象を何年も見てきました。小さなコードを単独で何度も繰り返し実行して速度をテストすると、VMはコードを最適化します。それは、その特定のテストにのみ意味のある最適化を行います。ベンチマークは、別のプログラム内で同じコードを実行する場合、または同じコードを異なる方法で最適化するいくつかの異なるテストを実行した直後に実行する場合と比較して、速度が100倍向上します。

于 2013-10-01T14:36:29.660 に答える
1

私は実際にはJavaScriptエンジンに貢献しておらず、v8でいくつかの読み取り値しか持っていなかったため、私の答えは完全には正しくない可能性があります。

配列のウェル値(穴/ギャップのない通常の配列のみ、スパースではありません。スパース配列はオブジェクトとして扱われます。)はすべてポインターまたは固定長の数値です(v8では32ビット、31ビット整数の場合)最後にビットでタグ付けされてい0ます。それ以外の場合はポインタです)。

したがって、バイト数は配列全体で同じであるため、メモリ位置の検索はtypedArrayと何ら変わりはないと思います。ただし、オブジェクトの場合は、ボックス化解除レイヤーを1つ追加する必要があります。これは、通常のtypedArrayでは発生しません。

そしてもちろん、typedArraysにアクセスするときは、通常の配列のような型チェックはありません(ただし、ホットコード用にのみ生成される高度に最適化されたコードで削除される可能性があります)。

書き込みの場合、同じタイプであれば、それほど遅くなることはありません。別のタイプの場合、JSエンジンはそのためのポリモーフィックコードを生成する可能性がありますが、これは低速です。

jsperf.comでいくつかのベンチマークを作成して確認することもできます。

于 2012-11-11T05:57:49.790 に答える