web-dev-qa-db-ja.com

JavaScriptの型付き配列の利点は、Cでも同じまたは同様に機能することですか?

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

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

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

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

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

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

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

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

23
alex

型付き配列は、パフォーマンス上の理由から、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エンジンがそれらを簡単に実装できることを意味します。高度な最適化解除のすべてのサポートは必要ありません。
  • 非常に高度なエンジンを使用しても、最適化を使用できることを証明することは非常に困難であり、不可能な場合もあります。型付き配列は、エンジンがその周りで最適化できる必要がある証明のレベルを大幅に簡素化します。型付き配列から返される値は確かに特定の型であり、エンジンはその型の結果に合わせて最適化できます。通常の配列から返される値は、理論的にはどのタイプでもかまいません。エンジンは、常に同じタイプの結果になることを証明できない可能性があるため、生成されるコードの効率が低下します。したがって、型付き配列の周りのコードはより簡単に最適化されます。
  • 型付き配列は、間違いを犯す機会を取り除きます。誤ってオブジェクトを保存して、突然パフォーマンスが大幅に低下することはありません。

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

44
AshleysBrain

はい、あなたはほとんど正しいです。標準の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バイト値の配列)があるため、データ(オーディオ、ビデオなど)と直接インターフェイスするために使用できます。

7
reece

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

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

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

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

3
David Leppik

私はjavascriptエンジンに実際に貢献しているわけではなく、v8でいくつかの読み取り値しか持っていなかったので、私の答えは完全に真実ではないかもしれません。

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

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

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

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

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

1