V8でこのコードスニペットを使用すると<=が<より遅いのはなぜですか?


166

スライドを読んでいますが、V8JavaScriptの速度制限を破っています。以下のコードのような例があります。なぜこの場合<=より遅いのか理解できません<が、誰か説明できますか?コメントをいただければ幸いです。

スロー:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
} 

(ヒント:プライムは、prime_countの長さの配列です)

もっと早く:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i < this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
} 

[詳細]速度の向上は重要です。私のローカル環境テストでは、結果は次のとおりです。

V8 version 7.3.0 (candidate) 

スロー:

 time d8 prime.js
 287107
 12.71 user 
 0.05 system 
 0:12.84 elapsed 

もっと早く:

time d8 prime.js
287107
1.82 user 
0.01 system 
0:01.84 elapsed

10
@DacreDenny計算困難<=<理論的には、すべての最新のプロセッサ(通訳)での実際の実装の両方で、同じです。
TypeIA 2018

1
私はドキュメントを読みましたが、mainその関数をループで呼び出して実行するコードがある25000ため、変更を行う全体的な反復回数が大幅に少なくなっています。また、配列の長さが5 array[5]undefined場合、配列のインデックス作成が開始されるため、取得しようとすると、制限を超えて値が与えられ0ます。
Shidersz

1
この質問で、速度の改善がどれほど得られるか(たとえば、5倍速い)が説明され、追加のイテレーションによって人々が見捨てられることがないようにすると役立ちます。スライドの速さを見つけようとしましたが、たくさんあり、見つけられませんでした。それ以外の場合は自分で編集します。
キャプテンマン

@CaptainManそうです、正確な速度の向上は、スライドからいくつかの異なる問題を一度にカバーするため、スライドから収集するのは困難です。しかし、この講演の後の講演者との私の会話では、このテストケースの1回の追加の反復から期待できるようなわずかな割合ではなく、大きな違いがあることが確認されました。大きさ以上。そして、その理由は、配列の境界の外側を読み取ろうとすると、V8が最適化されていない配列形式にフォールバックする(または当時はフォールバックする)ためです。
Michael Geary

3
使用しているバージョンを比較することは有用であるかもしれない<=が、そうでない場合とまったく同じ働き<行うことによってバージョンi <= this.prime_count - 1。これにより、「余分な反復」問題と「アレイの終わりを過ぎたもの」問題の両方が解決されます。
TheHansinator

回答:


132

私はGoogleでV8に取り組んでおり、既存の回答とコメントに加えていくつかの追加の洞察を提供したいと考えていました。

参考までに、スライドの完全なコード例を以下に示します。

var iterations = 25000;

function Primes() {
  this.prime_count = 0;
  this.primes = new Array(iterations);
  this.getPrimeCount = function() { return this.prime_count; }
  this.getPrime = function(i) { return this.primes[i]; }
  this.addPrime = function(i) {
    this.primes[this.prime_count++] = i;
  }
  this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
      if ((candidate % this.primes[i]) == 0) return true;
    }
    return false;
  }
};

function main() {
  var p = new Primes();
  var c = 1;
  while (p.getPrimeCount() < iterations) {
    if (!p.isPrimeDivisible(c)) {
      p.addPrime(c);
    }
    c++;
  }
  console.log(p.getPrime(p.getPrimeCount() - 1));
}

main();

何よりもまず、パフォーマンスの違いは<and <=演算子とは直接関係ありません。したがって<=、コードで回避するためだけにフープを飛び越えないでください。スタックオーバーフローで遅いと読みますが、そうではありません。


第二に、人々は配列が「ホーリー」であることを指摘しました。これはOPの投稿のコードスニペットからは明らかではありませんでしたが、初期化するコードを見ると明らかですthis.primes

this.primes = new Array(iterations);

これにより、配列が完全に満たされた/パックされた/隣接した場合でも、V8の種類HOLEY要素を持つ配列が生成されます。一般に、穴あき配列での操作はパック配列での操作よりも遅くなりますが、この場合の違いは無視できます。つまり、内のループでヒットするたびに、(穴を防ぐために)Smi(小さな整数)チェックが1回追加さthis.primes[i]れますisPrimeDivisible。大きな問題ではない!

TL; DR ここにあるアレイHOLEYは問題ではありません。


他の人は、コードが範囲外を読み取ると指摘しました。一般に、配列の長さを超えて読み取ることを避けることをお勧めします。この場合、パフォーマンスの大幅な低下は実際には避けられます。しかし、なぜですか?V8は、これらの範囲外のシナリオの一部を処理でき、パフォーマンスへの影響はわずかです。それでは、この特定のケースの何がそれほど特別なのでしょうか?

アウトオブバウンドがで結果を読んでthis.primes[i]いるundefinedこの行に:

if ((candidate % this.primes[i]) == 0) return true;

そして、それが本当の問題に%つながります:演算子は現在、非整数のオペランドで使用されています!

  • integer % someOtherInteger非常に効率的に計算できます。この場合、JavaScriptエンジンは高度に最適化されたマシンコードを生成できます。

  • integer % undefined一方Float64Modundefinedはdoubleとして表されるため、効率が低下します。

コードスニペットは、実際に変更することによって改善することができる<=には<、この行に:

for (var i = 1; i <= this.prime_count; ++i) {

... <=がよりも優れた演算子であるからではなく<、これがこの特定のケースで範囲外の読み取りを回避するからです。


1
コメントは拡張ディスカッション用ではありません。この会話はチャットに移動さました
サミュエルLiew

1
100%完成させるために、isPrimeDivisibleのthis.primes [i]のキー付きロードICは、V8で予期せず巨大化します。それはバグのようです:bugs.chromium.org/p/v8/issues/detail?id
Mathias Bynens

226

他の回答やコメントでは、2つのループの違いは、最初のループが2番目のループよりも1回多い反復を実行することです。これは真実ですが、25,000要素に成長する配列では、1回の反復で多少の違いが生じるだけです。概算として、成長する平均の長さが12,500であると仮定すると、予想される差は約1 / 12,500、つまり0.008%にすぎないはずです。

ここでのパフォーマンスの違いは、1つの追加の反復によって説明されるよりもはるかに大きく、問題はプレゼンテーションの終わり近くで説明されています。

this.primes 連続した配列(すべての要素が値を保持)であり、要素はすべて数値です。

JavaScriptエンジンは、このような配列を、たまたま数値を含んでいて他の値を含んだり、値を含まなかったりする可能性のあるオブジェクトの配列ではなく、実際の数値の単純な配列に最適化する場合があります。最初の形式は、アクセスがはるかに高速です。必要なコードが少なく、配列がはるかに小さいため、キャッシュにうまく収まります。ただし、この最適化された形式の使用を妨げる可能性があるいくつかの条件があります。

1つの条件は、配列要素の一部が欠落している場合です。例えば:

let array = [];
a[0] = 10;
a[2] = 20;

今の価値はa[1]何ですか?それは値を持ちません。(値を持っていると言っても正しくありません。値undefinedを含む配列要素は、undefined完全に欠落している配列要素とは異なります。)

これを数値のみで表す方法はないため、JavaScriptエンジンは最適化されていない形式を使用する必要があります。a[1]他の2つの要素のような数値が含まれている場合、配列は数値のみの配列に最適化される可能性があります。

プレゼンテーションで説明されているように、配列が非最適化形式に強制されるもう1つの理由は、配列の境界外の要素にアクセスしようとした場合です。

<=配列の最後を超えて要素を読み取ろうとする最初のループ。最後の追加の反復では次の理由により、アルゴリズムは引き続き正しく機能します。

  • this.primes[i]は配列の終わりを超えているundefinedため、iと評価されます。
  • candidate % undefined(の任意の値に対してcandidate)はに評価されNaNます。
  • NaN == 0はに評価されfalseます。
  • したがって、return true実行されません。

つまり、追加の反復が発生しなかったかのように、残りのロジックには影響がありません。このコードは、追加の反復を行わない場合と同じ結果を生成します。

しかし、そこに到達するために、配列の終わりを超えて存在しない要素を読み取ろうとしました。これにより、アレイは最適化されなくなります-または、少なくともこの講演の時点ではそうでした。

2番目のループは<、配列内に存在する要素のみを読み取るため、最適化された配列とコードを使用できます。

問題は講演の90〜91ページで説明されており、関連する議論はその前後のページで行われています。

私はたまたまこのGoogle I / Oプレゼンテーションに出席し、その後スピーカー(V8作成者の1人)と話しました。私は自分のコードで、特定の状況を最適化するための誤った(後からの)試みとして配列の最後を超えて読み取ることを含む手法を使用していました。彼は、配列の最後を超えて読み込もうとすると、単純な最適化されたフォーマットが使用できなくなることを確認しました。

V8の作成者の発言が依然として真実である場合、配列の最後を超えて読み取ると、配列の最適化が妨げられ、低速なフォーマットにフォールバックする必要があります。

現在のところ、V8が改善されてこのケースを効率的に処理できるようになったか、他のJavaScriptエンジンが異なる方法で処理した可能性があります。どちらかはわかりませんが、この非最適化はプレゼンテーションで話していたものです。


1
配列がまだ連続していると確信しています-メモリレイアウトを変更する理由はありません。重要なのは、プロパティアクセスの範囲外のインデックスチェックを最適化できずundefined、別の計算につながる数値の代わりにコードが提供される場合があることです。
Bergi、

1
@Bergi私はJS / V8のエキスパートではありませんが、GC言語のオブジェクトはほとんどの場合、実際のオブジェクトへの参照です。GCオブジェクトの存続期間は結びついていないため、参照が連続していても、これらの実際のオブジェクトには独立した割り当てがあります。オプティマイザはこれらの独立した割り当てを隣接するようにパックできますが、(a)メモリが急増し、(b)反復される2つの連続したブロック(参照と参照されるデータ)が1つではなく繰り返されます。非常識なオプティマイザが参照と参照されるデータをインターリーブし、メモリストライプを所有する配列を持つことができると思います...
Yakk-Adam Nevraumont

1
@Bergi最適化されていない場合でも配列は連続している可能性がありますが、配列要素は最適化されている場合と同じ型ではありません。最適化されたバージョンは、追加の綿毛のない単純な数値の配列です。最適化されていないバージョンは、オブジェクトの配列(JavaScriptではなく内部オブジェクト形式Object)です。これは、配列内のデータ型の混合をサポートする必要があるためです。上で述べたように、供給されるループ内のコードundefinedはアルゴリズムの正確さに影響を与えません-それは計算をまったく変更しません(あたかも余分な反復が行われなかったかのようです)。
Michael Geary

3
@Bergiこの講演を行ったV8の作者は、配列の境界外での読み取りを試みると、配列がタイプの混合であるかのように処理されると述べています:最適化された数値のみの形式ではなく、配列を非最適化して汎用フォーマット。最適化されたケースでは、Cプログラムで使用する可能性のある数値の単純な配列です。最適化されていない場合は、Value任意のタイプの値への参照を保持できるオブジェクトの配列です。(私は名前を作りましたValueが、要点は、配列要素が単純な数値ではなく、数値または他の型をラップするオブジェクトであることです。)
Michael Geary

3
私はV8で作業しています。問題の配列はHOLEY、それを使用して作成されたためにマークされますnew Array(n)(ただし、コードのこの部分は OPに表示されませんでした)。HOLEY配列はHOLEY、後でいっぱいになった場合でも、V8永久に残ります。とはいえ、この場合、アレイがホーリーであることはパフォーマンスの問題の原因ではありません。これは、(穴を防ぐために)各反復で追加のSmiチェックを実行する必要があることを意味するだけで、大したことではありません。
Mathias Bynens 2018

19

TL; DR遅いループは、配列の「範囲外」へのアクセスが原因です。これにより、エンジンは、最適化をほとんどまたはまったく行わずに関数を再コンパイルするか、これらの最適化のいずれかで関数をコンパイルしなくなります( (JIT-)コンパイラが最初のコンパイル「バージョン」の前にこの状態を検出/疑った場合)、以下の理由を読んでください。


誰かこれを言わなければならない(まったく驚いていない):
OPのスニペットは、JavaScriptの「配列」が索引付けされていることを概説/強調することを意図した初心者プログラミング本のデファクトの例であることがかつてあった1ではなく0として、一般的な「初心者の間違い」の例として使用します(「プログラミングエラー」というフレーズをどのように回避したか気になりませんか;)):範囲外の配列アクセス

実施例1:(常にES262で)0ベースのインデックスを使用して、5つの要素の(インデックス間のギャップなしに連続している(手段)と、各インデックスに実際の要素)。
Dense Array

var arr_five_char=['a', 'b', 'c', 'd', 'e']; // arr_five_char.length === 5
//  indexes are:    0 ,  1 ,  2 ,  3 ,  4    // there is NO index number 5



したがって、実際には<vs <=(または「1つの追加の反復」)間のパフォーマンスの違いについて話しているわけではありません
が、「正しいスニペット(b)が誤ったスニペット(a)よりも高速に実行されるのはなぜですか」

答えは2つあります(ただし、ES262言語の実装者から見ると、どちらも最適化の形式です)。

  1. データ表現:配列をメモリ内で内部的に表現/格納する方法(オブジェクト、ハッシュマップ、「実際の」数値配列など)
  2. 関数型マシンコード:これらの「配列」にアクセス/処理(読み取り/変更)するコードをコンパイルする方法

項目1は受け入れられた回答で十分に(そして正しくは私見で)説明されていますが項目2のコンパイルに 2ワード(「コード」)しか費やしていません。

より正確には、JITコンパイル、さらに重要なJIT- REコンパイル!

言語仕様は、基本的には一連のアルゴリズム(「定義された最終結果を達成するために実行する手順」)の単なる説明です。結局のところ、これは言語を記述する非常に美しい方法です。そして、エンジンが指定された結果を達成するために使用する実際の方法を実装者に任せ、定義された結果を生成するためのより効率的な方法を考え出す十分な機会を与えます。仕様適合エンジンは、定義された入力に対して仕様適合結果を提供する必要があります。

現在、JavaScriptコード/ライブラリ/使用量が増加し、「実際の」コンパイラが使用するリソース(時間/メモリなど)の量を記憶しているため、Webページにアクセスするユーザーをそれほど長く待てない(そしてそれらを要求する)ことができないことは明らかです。多くのリソースを利用できるようにするためです)。

次の単純な関数を想像してみてください。

function sum(arr){
  var r=0, i=0;
  for(;i<arr.length;) r+=arr[i++];
  return r;
}

完全にクリアですよね?追加の説明は必要ありませんよね?戻り値の型はNumber、ですよね?
まあ..いいえ、いいえ&いいえ...それはあなたが名前付き関数パラメータに渡す引数に依存しますarr...

sum('abcde');   // String('0abcde')
sum([1,2,3]);   // Number(6)
sum([1,,3]);    // Number(NaN)
sum(['1',,3]);  // String('01undefined3')
sum([1,,'3']);  // String('NaN3')
sum([1,2,{valueOf:function(){return this.val}, val:6}]);  // Number(9)
var val=5; sum([1,2,{valueOf:function(){return val}}]);   // Number(8)

問題を見ますか?次に、これが可能性のある膨大な順列をかろうじて削っているだけであると考えます...完了するまで、関数RETURNがどのような種類のTYPEであるかさえわかりません...

今度は、この同じ関数コードが、文字どおり(ソースコードで)完全に記述され、プログラム内で動的に生成された「配列」の両方で、さまざまなタイプまたは入力のバリエーションで実際に使用されていると想像してください。

したがって、関数sumJUST ONCE をコンパイルする場合、すべてのタイプの入力に対して常に仕様定義の結果を返す唯一の方法は、明らかに、すべての仕様規定のメインANDサブステップを実行することによってのみ、仕様に準拠した結果を保証できます。 (名前のないpre-y2kブラウザのように)。最適化(仮定がないため)とデッドスローインタープリタードスクリプト言語は残っていません。

JITコンパイル(ジャストインタイムのJIT)は現在人気のあるソリューションです。

したがって、関数の動作、戻り、受け入れに関する仮定を使用して、関数のコンパイルを開始します。
(予期しない入力を受け取ったなどの理由で)関数が仕様に準拠していない結果を返し始める可能性があるかどうかを検出するために、可能な限り簡単なチェックを考え出します。次に、以前のコンパイル結果を捨てて、より精巧なものに再コンパイルし、すでに持っている部分的な結果をどうするかを決定し(信頼できるか、確実に再計算できるか)、関数をプログラムに結び付け、再試行。最終的には仕様のように段階的なスクリプト解釈にフォールバックします。

これにはすべて時間がかかります!

すべてのブラウザがそれぞれのエンジンで動作します。サブバージョンごとに、物事が改善され、後退します。文字列は歴史のある時点で本当に不変の文字列であったため(つまり、array.joinは文字列の連結よりも高速でした)、問題を緩和するロープ(または類似のもの)を使用します。どちらも仕様に準拠した結果を返し、それが重要です!

簡単に言えば、JavaScriptの言語のセマンティクスが(OPの例のこのサイレントバグのように)しばしば後戻りしたからといって、「愚かな」ミスがコンパイラが高速のマシンコードを吐き出す可能性が高まるということではありません。これは、「通常」正しい指示を書いたことを前提としています:(プログラミング言語の)「ユーザー」が持っている必要のある現在のマントラは、コンパイラを支援し、必要なものを説明し、一般的なイディオムを支持します(基本的な理解のためにasm.jsからヒントを取得します)どのブラウザが最適化を試みることができるか、およびその理由)。

このため、パフォーマンスについて話すことは両方とも重要ですが、地雷原もです(そして、地雷原のため、私は本当にいくつかの関連資料を指して(そして引用して)終わらせたいと思います:

存在しないオブジェクトプロパティや範囲外の配列要素にアクセスするとundefined、例外が発生する代わりに値が返されます。これらの動的機能により、JavaScriptでのプログラミングが便利になりますが、JavaScriptを効率的なマシンコードにコンパイルすることも困難になります。

...

効果的なJIT最適化の重要な前提は、プログラマーがJavaScriptの動的機能を体系的に使用することです。たとえば、JITコンパイラは、オブジェクトプロパティが特定のタイプのオブジェクトに特定の順序で追加されることが多い、または範囲外の配列アクセスがほとんど発生しないという事実を利用しています。JITコンパイラーは、これらの規則性の仮定を利用して、実行時に効率的なマシンコードを生成します。コードブロックが前提条件を満たしている場合、JavaScriptエンジンは効率的な生成されたマシンコードを実行します。それ以外の場合、エンジンはより遅いコードまたはプログラムの解釈にフォールバックする必要があります。

出典:
"JITProf:Pinpointing JIT-unfriendly JavaScript Code"
Berkeley出版物、2014年、Liang Gong、Michael Pradel、Koushik Sen著
http://software-lab.org/publications/jitprof_tr_aug3_2014.pdf

ASM.JS(境界外の配列アクセスも望まない):

事前コンパイル

asm.jsはJavaScriptの厳密なサブセットであるため、この仕様では検証ロジックのみが定義されています。実行セマンティクスはJavaScriptの単純なものです。ただし、検証済みのasm.jsは、事前(AOT)コンパイルに適しています。さらに、AOTコンパイラーによって生成されるコードは非常に効率的で、以下の機能を備えています。

  • 整数および浮動小数点数のボックス化されていない表現。
  • ランタイム型チェックがない;
  • ガベージコレクションがないこと。そして
  • 効率的なヒープのロードとストア(実装戦略はプラットフォームによって異なる)。

検証に失敗したコードは、解釈やジャストインタイム(JIT)コンパイルなどの従来の手段による実行にフォールバックする必要があります。

http://asmjs.org/spec/latest/

そして最後にhttps://blogs.windows.com/msedgedev/2015/05/07/bringing-asm-js-to-chakra-microsoft-edge/
境界を削除するときのエンジンの内部パフォーマンスの改善に関する小さなサブセクションがありました-チェック(境界チェックをループの外に引き上げるだけで、既に40%の改善がありました)。



編集:
複数のソースがJIT再コンパイルのさまざまなレベルから解釈まで話していることに注意してください。

OPのスニペットに関する上記の情報に基づく理論的な例

  • isPrimeDivisibleの呼び出し
  • 一般的な仮定(境界外のアクセスがないなど)を使用してisPrimeDivisibleをコンパイルします。
  • 仕事する
  • BAM、突然配列のアクセスが範囲外になりました(最後にあります)。
  • クラップ、エンジンは言う、異なる(より少ない)仮定を使用してisPrimeDivisibleを再コンパイルしてみましょう。このサンプルエンジンは、現在の部分的な結果を再利用できるかどうかを判断しようとしません。
  • 遅い関数を使用してすべての作業を再計算します(うまくいけばそれは終了します。それ以外の場合は繰り返し、今回はコードを解釈します)。
  • 結果を返す

したがって、その後の時間は
次のとおりです。最初の実行(最後に失敗)+すべての作業を繰り返しごとに遅いマシンコードを使用してもう一度行う+再コンパイルなどは、この理論的な例では明らかに2倍以上かかります !



編集2:( 免責事項:以下の事実に基づく推測)
私が考えれば考えるほど、この回答は、誤ったスニペットa(またはスニペットbのパフォーマンスボーナス)に対するこの「ペナルティ」の主な理由を実際に説明できると思う、あなたの考え次第で)正確になぜ私がそれを(スニペットa)プログラミングエラーと呼ぶのを恐れているのか:

これthis.primesが「密配列」の純粋な数値であると仮定するのはかなり魅力的です。

  • ソースコード内のハードコードされたリテラル(コンパイル前にすべてがコンパイラ認識されているため、「実際の」配列になることが知られている優れた候補)または
  • 最も可能性が高いのは、事前にサイズ設定された(new Array(/*size value*/))を昇順で埋める数値関数を使用して生成されたものです(「本物の」配列になる別の長い間知られている候補)。

我々はまた、ことを知ってprimes、配列の長さがされ、キャッシュされたようprime_count!(意図と固定サイズを示します)。

また、ほとんどのエンジンは最初に配列を(必要なときに)コピーオンモディファイとして渡すため、(変更しない場合)配列の処理がはるかに高速になります。

したがって、Array primesはおそらく作成後に変更されない内部で最適化された配列である可能性が最も高く(作成後に配列を変更するコードがない場合はコンパイラーに簡単にわかる)、したがってすでに(該当する場合)かなり多くのエンジンが)、最適化された形で保存されているかのようでしたTyped Array

sum関数の例で明確にしようとしたように、渡される引数は、実際に何が必要か、その特定のコードがマシンコードにコンパイルされる方法に大きく影響します。関数にa Stringを渡してsumも文字列は変更されませんが、関数のJITコンパイル方法が変更されます。配列をに渡すと、sumマシンコードの異なる(おそらく、このタイプの追加、または渡されたオブジェクトの「シェイプ」)バージョンがコンパイルされます。

Typed_Arrayのようなprimes配列をオンザフライでsome_elseに変換するのは少しおかしく思えますが、コンパイラーはこの関数がそれを変更することさえないことを知っています!

2つのオプションを残すこれらの仮定の下:

  1. 範囲外を想定して数値クランチャーとしてコンパイルし、最後に範囲外の問題に遭遇し、再コンパイルしてやり直します(上記の編集1の理論例で概説)
  2. コンパイラはすでに範囲外のアクセスを事前に検出(または疑って​​いますか?)し、関数は、渡された引数がスパースオブジェクトであるかのようにJITコンパイルされ、その結果、機能的なマシンコードが遅くなります(チェック/変換/強制が増えるため)等。)。言い換えると、関数は特定の最適化に適格ではなかったため、「スパース配列」(のような)引数を受け取ったかのようにコンパイルされました。

私は今、これらの2つのうちのどれがそれであるのか本当に疑問に思います!


2
根本的な問題のいくつかについての良い議論-しかし、あなたはほとんど(最後の文で)答えをほとんど説明しません。たぶん、tl; drを一番上に追加しますか?例「ループが遅いのは、境界配列を超えているためです。これにより、エンジンは最適化せずにループを再評価する必要があります。その理由を学ぶために読んでください。」
brichins

@brichins:おかげで、提案のおかげで、2回目の追加編集を踏まえて少し言い換えましたが、考えれば考えるほど、上部のステートメントも実際には正しいように見えます
GitaarLAB

6

それに科学性を加えるために、これがjsperfです

https://jsperf.com/ints-values-in-out-of-array-bounds

これは、境界内にとどまりながら、整数で満たされた配列の制御ケースとモジュラー演算を行うループをテストします。5つのテストケースがあります。

  • 1.範囲外のループ
  • 2.穴あきアレイ
  • 3. NaNに対するモジュラー演算
  • 4.完全に未定義の値
  • 5.を使用して new Array()

最初の4つのケースはパフォーマンスにとって本当に悪いことを示しています。範囲外のループは他の3つよりも少し優れていますが、4つすべてが最良の場合よりも約98%遅くなっています。ケースはわずか数パーセント遅く、ほとんど生の配列として良いようです。
new Array()

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.