ループは逆に本当に高速ですか?


268

これを何度も聞いたことがあります。逆算すると、JavaScriptループは本当に速くなりますか?もしそうなら、なぜですか?逆ループの方が速いことを示すテストスイートの例をいくつか見ましたが、その理由についての説明はありません。

これは、ループが終了したかどうかを確認するためにチェックするたびにプロパティを評価する必要がなくなり、最終的な数値に対してチェックするだけだからだと思います。

すなわち

for (var i = count - 1; i >= 0; i--)
{
  // count is only evaluated once and then the comparison is always on 0.
}

6
へへ。それは不確定にかかります。私を試してください
StampedeXV

5
for上限(へへ、下限)ループ制御変数を定義したり、オブジェクトからフェッチしたりする必要がないため、逆方向ループの方が高速です。定数ゼロです。
Josh Stodola、

88
本当の違いはありません。ネイティブループ構造は常に非常に高速になります。彼らのパフォーマンスについて心配しないでください。
James Allardice 2012年

24
@Afshin:このような質問について、あなたが参照しいる記事を見せてください。
オービットでの軽量レース

22
非常にローエンドのバッテリー駆動デバイスには、主に重要な違いがあります。違いは、i--ではループの終わりを0と比較するのに対し、i ++では数値> 0と比較することです。パフォーマンスの違いは20ナノ秒のようなものだと思います(cmp ax、0 vs. cmp axのようなもの) 、BX) -何もありませんが、あなた毎秒回のループ数千人が、なぜ20ナノ秒を持っていない場合は、各:)のために獲得
luben

回答:


903

それi--よりも速くはありませんi++。実際、どちらも同じくらい高速です。

昇順ループで時間がかかるのは、それぞれのi配列のサイズを評価することです。このループでは:

for(var i = array.length; i--;)

.length宣言するとき、評価は1回だけですがi、このループでは

for(var i = 1; i <= array.length; i++)

.length増分するたびにi、チェックするときに評価しますi <= array.length

ほとんどの場合、この種の最適化について心配する必要はありません


23
array.lengthの変数を導入して、forループの先頭で使用する価値はありますか?
ragatskynet 2012年

39
@ragatskynet:いいえ、あなたがベンチマークを設定していてポイントを作りたいのでなければ。
2012年

29
@ragatskynet状況によって異なります。.length特定の回数を評価したり、新しい変数を宣言して定義したりする方が速くなりますか?ほとんどの場合、length評価に非常に費用がかかるのでない限り、これは時期尚早(かつ間違った)最適化です。
alestanis 2012年

10
@ Dr.Dredel:これは比較ではなく、評価です。0よりも評価が高速ですarray.length。まあ、おそらく。
明度レース軌道上に

22
言及する価値があるのは、RubyやPythonなどのインタープリター型言語について話していることです。コンパイルされた言語(Javaなど)は、コンパイラレベルで最適化されて.lengthおり、宣言のfor loop有無に関係なく、これらの違いを「スムーズ」にします。
Haris Krajina

198

この男は、多くのブラウザで、JavaScriptの多くのループを比較しました。彼はテストスイートも持っているので、自分で実行できます。

すべてのケースで(読み取りで1つを見落とさない限り)最速のループは次のとおりです。

var i = arr.length; //or 10
while(i--)
{
  //...
}

ニース:)しかし、テストされた「for」ループはありません。そして、それはテストされた最速のループでした。
SvenFinke 2009

11
興味深いですが、前減分がさらに速くなるのではないかと思います。iの中間値を格納する必要がないためです。
tvanfosson 2009

私の記憶が正しければ、ハードウェアコースの教授は、0であるか0でないかでのテストは可能な限り最速の「計算」だと言いました。while(i--)のテストは常に0のテストです。たぶんそれがこれが最も速い理由ですか?
StampedeXV、

3
@tvanfossonプリデクリメント--iする場合はvar current = arr[i-1];、ループ内で使用する必要があります。そうしないと、ループが1つオフになります...
DaveAlger

2
2番目のケースではプロセッサがデクリメントして新しい値に対してテストする必要があるため(命令間にデータ依存関係があるため)、i--は--iよりも高速になる可能性があると聞きましたが、最初のケースではプロセッサは既存の値をテストし、しばらくしてから値を減らします。これがJavaScriptに適用されるのか、非常に低レベルのCコードにのみ適用されるのかはわかりません。
marcus

128

私はこの答えで全体像を示すようにします。

括弧で囲まれた以下の考えは、問題を最近テストしたところまで私の信念でした。

[[ C / C ++のような低水準言語に関しては、変数がゼロ(または非ゼロ)のときにプロセッサーが特別な条件付きジャンプコマンドを持つようにコードがコンパイルされます。 あなたはこのくらいの最適化を心配している場合また、あなたが行くことができる代わりのため、一方、シングルプロセッサのコマンドである手段。]]
++ii++++ii++j=i+1, i=j

本当に高速なループは、それらを展開することで実行できます。

for(i=800000;i>0;--i)
    do_it(i);

それはよりも遅くなる可能性があります

for(i=800000;i>0;i-=8)
{
    do_it(i); do_it(i-1); do_it(i-2); ... do_it(i-7);
}

しかし、これの理由は非常に複雑になる可能性があります(ゲームのプロセッサコマンドの前処理とキャッシュ処理の問題があります)。

高レベル言語に関しては、JavaScriptのように、ライブラリー(ループ用の組み込み関数)に依存している場合は最適化できます。それがどのように最良に行われるかを彼らに決めさせてください。

したがって、JavaScriptでは、次のようなものを使用することをお勧めします

array.forEach(function(i) {
    do_it(i);
});

また、エラーが発生しにくく、ブラウザーがコードを最適化する機会があります。

[備考:ブラウザーだけでなく、簡単に最適化するスペースもあるので、forEach関数を再定義して(ブラウザーに依存)、最新の最良の手法を使用してください!:) @AMKは特別なケースではarray.popor を使用する価値があると言っていarray.shiftます。その場合は、カーテンの後ろに置いてください。最大限のやり過ぎはにオプションを追加することでforEachループアルゴリズムを選択します。]

さらに、低レベル言語の場合も、可能であれば、スマートライブラリ関数を使用して複雑なループ操作を実行することをお勧めします。

これらのライブラリは、物事(マルチスレッド)を背後に配置することもでき、専門のプログラマーがそれらを最新に保ちます。

私はもう少し精査しましたが、C / C ++では、5e9 =(50,000x100,000)の演算であっても、@ alestanisが言うように定数に対してテストを行っも、上昇と下降に違いはありません。(JsPerfの結果は一貫性のない場合がありますが、概して同じことです。大きな違いを生み出すことはできません。)
したがって、--iたまたま「かなり」のことです。それはあなたをより良いプログラマーのように見せるだけです。:)

一方、この5e9の状況でのアンロールでは、10秒経過すると12秒から2.5秒に、20秒経過すると2.1秒にダウンしました。それは最適化なしでした、そして、最適化は物事を計り知れない少しの時間に引き下げました。:)(アンロールは、上記の方法またはを使用して実行できますがi++、JavaScriptで先に進むことはできません。)

全体として:i-- / i++++i/ i++面接との違いを維持し、array.forEach可能な場合は、または他の複雑なライブラリ関数に固執します。;)


18
キーワードは「できる」です。展開されたループは、元のループよりも遅い場合があります。最適化するときは常に測定を行い、変更がどのような影響を与えたかを正確に把握してください。
jalf

1
@jalf確かに+1。異なるループ解除長さ(> = 1)は効率が異なります。そのため、可能な場合はこのジョブをライブラリーに任せたほうが便利です。ブラウザーが異なるアーキテクチャーで実行されることは言うまでもありませんarray.each(...)。そのため、ユーザーが方法を決定した方がよいでしょう。彼らがループを解く単純なforループを試してみようとは思わない。
バーニー

1
@BarnabasSzabolcs問題はJavaScript専用で、Cや他の言語ではありません。JSではそこの違いは、以下の私の答えを参照してください。質問には適用されませんが、+ 1の正解です。
AMK、

1
そして、あなたはそこに行く- +1あなたを取得するためにGreat Answerバッジを。本当に素晴らしい答えです。:)
Rohit Jain

1
APL / A ++も言語ではありません。人々はこれらを使用して、言語の詳細ではなく、それらの言語が共有するものに興味がないことを表現します。
Barney

33

i-- と同じくらい速いです i++

以下のこのコードはあなたのコードと同じくらい高速ですが、追加の変数を使用しています:

var up = Things.length;
for (var i = 0; i < up; i++) {
    Things[i]
};

アレイのサイズを毎回評価しないことをお勧めします。大きなアレイの場合、パフォーマンスの低下が見られます。


6
ここは明らかに間違っています。ループ制御には追加の内部変数(i以上)が必要になり、JavaScriptエンジンによっては、コードを最適化する可能性が制限される場合があります。JITは単純なループを直接マシンのオペコードに変換しますが、ループの変数が多すぎると、JITは最適化できなくなります。一般に、JITが使用するアーキテクチャまたはCPUレジスタによって制限されます。一度初期化してダウンするのは最もクリーンなソリューションであり、そのため、あらゆる場所で推奨されています。
Pavel P

追加の変数は、一般的なインタプリタ/コンパイラのオプティマイザによって削除されます。ポイントは「0と比較」です-詳細な説明については私の回答を参照してください。
H.-Dirk Schmitt、

1
ループ内に「上」を置く方が良い:for (var i=0, up=Things.length; i<up; i++) {}
thdoan

22

あなたはこの主題に興味を持っているので、JavaScriptループベンチマークについてのGreg Reimerのブログ投稿を見てください。JavaScriptでループをコーディングする最も速い方法何ですか?

JavaScriptでループをコーディングするさまざまな方法のループベンチマークテストスイートを作成しました。これらのいくつかはすでに存在していますが、ネイティブ配列とHTMLコレクションの違いを認めたものは見つかりませんでした。

を開くことにより、ループでパフォーマンステストを行うこともできますhttps://blogs.oracle.com/greimer/resource/loop-test.html(たとえば、JavaScriptがNoScriptなどによってブラウザーでブロックされている場合は機能しません)。

編集:

ミラノAdamovskyによって作成された、より最近のベンチマークでは、実行時に実行することができ、ここで別のブラウザのために。

以下のためのMac OS X 10.6上のFirefox 17.0でのテスト私は、次のループを得ました:

var i, result = 0;
for (i = steps - 1; i; i--) {
  result += i;
}

先行する最速として:

var result = 0;
for (var i = steps - 1; i >= 0; i--) {
  result += i;
}

ベンチマークの例。


5
そのポストは現在4歳です。JavaScriptエンジンが更新された速度(ha!)を考えると、ほとんどのアドバイスは時代遅れになっている可能性があります。この記事ではChromeについてさえ言及しておらず、V8 JavaScriptエンジンの光沢があります。
Yi Jiang

うん、指摘してくれてありがとう。その時のイベントは、ループforの--iまたはi ++の間に大きな違いはありませんでした。
dreamcrash 2012年

19

そうではありません--か、++それは比較操作で、。では--、0との比較を使用できますが++、長さと比較する必要があります。プロセッサでは、ゼロとの比較が通常使用可能ですが、有限整数との比較は減算が必要です。

a++ < length

実際にコンパイルされます

a++
test (a-length)

そのため、コンパイル時にプロセッサで時間がかかります。


15

短い答え

通常のコードのために、特にのような高レベルの言語ではJavaScript、にはパフォーマンスの差がないi++とはi--

パフォーマンス基準は、forループと比較での使用ですステートメントでの使用です。

これはすべての高級言語に適用されます、JavaScriptの使用からほとんど独立しています。説明は、最終的な結果のアセンブラコードです。

詳細説明

ループでパフォーマンスの違いが発生する可能性があります。背景は、アセンブラコードレベルで、compare with 0は追加のレジスタを必要としない1つのステートメントにすぎ。

この比較はループのすべてのパスで発行され、測定可能なパフォーマンスの向上をもたらす可能性があります。

for(var i = array.length; i--; )

このような擬似コードに評価されます

 i=array.length
 :LOOP_START
 decrement i
 if [ i = 0 ] goto :LOOP_END
 ... BODY_CODE
 :LOOP_END

0はリテラル、つまり定数値であることに注意してください。

for(var i = 0 ; i < array.length; i++ )

このような擬似コードに評価されます(通常のインタプリタ最適化が想定されています):

 end=array.length
 i=0
 :LOOP_START
 if [ i < end ] goto :LOOP_END
 increment i
 ... BODY_CODE
 :LOOP_END

endCPUレジスタを必要とする変数であることに注意してください。これにより、コードで追加のレジスタスワッピングが呼び出される可能性があり、ステートメント内でより高価な比較ステートメントが必要になりますif

ちょうど私の5セント

高水準言語では、保守性を容易にする読みやすさは、マイナーなパフォーマンスの改善としてより重要です。

通常、配列の最初から最後までの古典的な反復の方が優れています。

配列の端から開始までのより速い反復は、望ましくない可能性がある逆のシーケンスになります。

ポストスクリプト

コメントに尋ねたとおり:の違い--ii--の評価でありますiデクリメントの前または後に。

最良の説明は、それを試すことです;-)これがBashの例です。

 % i=10; echo "$((--i)) --> $i"
 9 --> 9
 % i=10; echo "$((i--)) --> $i"
 10 --> 9

1
1+良い説明。ただ、質問のスコープの少し出て、あなたは違いを説明してください可能性--ii--もに?
Afshin Mehrabani、2012年

14

Sublime Text 2でも同じ推奨事項を見てきました。

すでに述べたように、主な改善はforループの各反復で配列の長さを評価しないことです。これはよく知られている最適化手法であり、配列がHTMLドキュメントの一部である場合(forすべてのli要素に対してa を実行する場合)はJavaScriptで特に効率的です。

例えば、

for (var i = 0; i < document.getElementsByTagName('li').length; i++)

よりはるかに遅い

for (var i = 0, len = document.getElementsByTagName('li').length; i < len; i++)

私が立っているところから、あなたの質問のフォームの主な改善点は、追加の変数を宣言しないことです(len私の例では)

しかし、私に尋ねると、全体のポイントはi++vs i--最適化ではなく、各反復で配列の長さを評価する必要がないことです(jsperfでベンチマークテストを確認できます)。


1
私はここで計算する単語に例外をとらなければなりません。Pavelの回答に関する私のコメントを参照してください。ECMA仕様では、配列の長さを参照しても、配列の長さは計算されないことが記載されています。
kojiro 2012年

「評価する」のが良い選択でしょうか?興味深い事実ですが、私はそれを知りませんでした
BBog

追加の変数は、一般的なインタプリタ/コンパイラのオプティマイザによって削除されます。同じことがarray.length評価にも当てはまります。ポイントは「0と比較」です-詳細な説明については私の回答を参照してください。
H.-Dirk Schmitt、

@ H.-DirkSchmitt常識的な答えは別として、JavaScriptの歴史の長い間、コンパイラーはルックアップチェーンのパフォーマンスコストを最適化していませんでした。AFAIK V8が最初に試した。
kojiro 2012年

@ H.-DirkSchmittは小次郎が言ったように、これはよく知られていて確立されたトリックです。最近のブラウザではもはや関連性がなくなったとしても、それが時代遅れになることはありません。その上、長さの新しい変数を導入するか、OPの質問のトリックを使用してそれを行うことは、それでも最良の方法です(imo)。これは賢いことであり、良い習慣です。コンパイラがJSでよくない方法でよく行われることを処理するように最適化されたという事実とは関係ないと思います
BBog

13

JavaScriptのi--方が速いと言っても意味がないと思いi++ます。

まず、 JavaScriptエンジンの実装に完全に依存しています。

次に、最も単純な構成がJIT処理されてネイティブ命令に変換されている場合は、i++vsi--はそれを実行するCPUに完全に依存します。つまり、ARM(携帯電話)では、1つの命令でデクリメントとゼロとの比較が実行されるため、0に下げる方が高速です。

おそらく、提案された方法は

for(var i = array.length; i--; )

しかし、提案された方法は、一方が他方より速いからではなく、単に

for(var i = 0; i < array.length; i++)

その後、毎回 array.lengthに評価する必要がありました(よりスマートなJavaScriptエンジンは、ループが配列の長さを変更しないことを理解できる可能性があります)。単純なステートメントのように見えますが、実際にはJavaScriptエンジンによって内部で呼び出される関数です。

もう1つの理由は、i--「高速」と見なされる理由は、JavaScriptエンジンがループを制御するために内部変数を1つだけ割り当てる必要があるためです(への変数var i)。array.lengthまたは他の変数と比較した場合、ループを制御するために複数の内部変数が必要であり、内部変数の数はJavaScriptエンジンの限られた資産です。ループで使用される変数が少ないほど、JITが最適化する可能性が高くなります。だからこそ、i--より速く考えることができます...


3
がどのようarray.lengthに評価されるかについては、慎重に言い表す価値があります。参考にしても長さは計算されません。(これは、配列のインデックスが作成または変更されるたびに設定されるプロパティです)。追加コストがある場合、それはJSエンジンがその名前の検索チェーンを最適化していないためです。
小次郎2012年

まあ、Ecma仕様の内容はわかりませんが、さまざまなJavaScriptエンジンの内部について知っているgetLength(){return m_length; }と、ハウスキーピングが含まれるため、簡単ではありません。しかし、逆に考えようとすると、実際に長さを計算する必要がある配列の実装を書くのは非常に独創的です:)
Pavel P

ECMA仕様で、長さプロパティが既に計算されている必要があります。length-あるプロパティこと-アレイインデックスを追加または変更されるたびに直ちに更新されなければなりません。
小次郎2012年

私が言おうとしていることは、考えてみれば仕様に違反することはかなり難しいということです。
Pavel P

1
その点でx86はARMに似ています。 dec/jnzinc eax / cmp eax, edx / jne
Peter Cordes

13

他の回答はどれもあなたの特定の質問に答えないようです(それらの半分以上はCの例を示し、低レベル言語について議論しているので、あなたの質問はJavaScriptに対するものです)私は自分で書くことにしました。

だから、ここに行きます:

単純な答え: i--実行するたびに0と比較する必要がないため、一般的に高速です。さまざまなメソッドのテスト結果は次のとおりです。

テスト結果:この jsPerf によって「証明された」ように、arr.pop()実際には最速のループです。しかし、に焦点を当て--ii--i++そして++iあなたがあなたの質問に尋ねたとして、ここではjsPerfは、まとめた結果(彼らは、複数のjsPerf年代からあり、以下のソースを参照してください)。

--i そして i--しながら、Firefoxで同じにi--速くChromeのです。

Chromeでは、基本的なforループ(for (var i = 0; i < arr.length; i++))は、i----iFirefoxで、それは遅くですが。

ChromeとFirefoxの両方で arr.length方がChromeの方が大幅に高速で、約170,000オペレーション/秒です。

大きな違いがなければ、 ++ii++ほとんどのブラウザよりも高速です、AFAIK、それはどのブラウザでも逆になることはありません。

短い要約: arr.pop()断然最速のループです。特に言及したループの場合i--、最速のループです。

ソース: http : //jsperf.com/fastest-array-loops-in-javascript/15 http://jsperf.com/ipp-vs-ppi-2

これがあなたの質問に答えてくれることを願っています。


1
pop私の知る限りでは、ループの大部分で配列サイズが0に減少しているため、テストは非常に高速に思えます。ただし、公平を期すために、このjsperfを各テストで同じ方法で配列を作成するように設計しました- .shift()私のいくつかのブラウザーでは勝者であるように見えますが、私が期待したものとは異なり
looping

言及する唯一の人であることに++i
賛成

12

これは、メモリへの配列の配置と、その配列にアクセスしている間のメモリページのヒット率によって異なります。

ヒット率が向上するため、列の順序で配列メンバーにアクセスする方が行の順序よりも高速になる場合があります。


1
OPだけが同じ行列を異なる順序でトラバースするのに異なる時間がかかる理由を尋ねた場合
dcow

1
オペレーティングシステムでメモリ管理がページングに基づいていることを考慮すると、プロセスがキャッシュページにないデータを必要とする場合、OSでページフォールトが発生し、ターゲットページをCPUキャッシュに移動して別のページに置き換える必要があるため、ターゲットページがCPUキャッシュにある場合よりも時間がかかる処理のオーバーヘッドを引き起こします。各行がページOSサイズよりも大きい大きな配列を定義し、行順にアクセスするとします。この場合、ページフォールト率が増加し、その配列への列順アクセスよりも結果が遅くなります。
Akbar Bayati 2012年

ページフォールトは、キャッシュミスとは異なります。メモリがディスクにページアウトされた場合にのみページ違反が発生します。メモリへの格納方法の順番で多次元配列にアクセスすると、ページフォールトが原因ではなく、キャッシュの局所性(フェッチ時にキャッシュラインのすべてのバイトを使用)が原因で高速になります。(ワーキングセットが、使用しているコンピューターに対して大きすぎる場合を除きます。)
Peter Cordes

10

私が最後に気にしたのは、6502アセンブリ(8ビット、ええ!)を書いたときです。大きな利点は、ほとんどの算術演算(特にデクリメント)がフラグのセットを更新したことです。Z「到達ゼロ」インジケータでした。

したがって、ループの最後で、DEC(デクリメント)とJNZ(ゼロでない場合はジャンプ)の2つの命令を実行しただけで、比較は必要ありません。


JavaScriptの場合は明らかに適用されません(そのようなオペコードを持たないCPUで実行されるため)。おそらくi--vsの背後にある本当の理由i++は、前者ではループのスコープに追加の制御変数を導入しないことです。以下の私の回答を参照してください...
Pavel P

1
そうです、実際には当てはまりません。しかし、これは非常に一般的なCスタイルであり、それに慣れている私たちにはきれいに見えます。:-)
ハビエル

この点で、x86とARMはどちらも6502に似ています。 x86ケースのdec/jnz代わりにinc/cmp/jne。空のループが速く実行されることはありません(どちらもブランチのスループットを飽和させます)が、カウントダウンするとループのオーバーヘッドがわずかに減少します。現在のIntel HWプリフェッチャーは、降順のメモリアクセスパターンに悩まされることもありません。古いCPUは、4つの逆方向ストリームと6または10の順方向ストリーム、IIRCのように追跡できると思います。
Peter Cordes 2015

7

それは、JavaScript(およびすべての言語)が最終的にCPUで実行するためのオペコードに変換されることで説明できます。CPUには常に、ゼロと比較するための単一の命令があります。これは非常に高速です。

余談ですが、countが常に>= 0であることを保証できる場合は、次のように簡略化できます。

for (var i = count; i--;)
{
  // whatever
}

1
ソースコードが短くても、必ずしも高速になるとは限りません。測定しましたか?
グレッグヒューギル

おっとこれは見逃した。そこにチャップの頭に釘。
ロバート

1
0との比較が異なるアセンブリソースを見たいのですが。数年になりますが、一度に大量のアセンブリコーディングを行ったので、私にとっては、0と比較/テストして、同等に速くできない方法を考えることはできませんでした。他の整数の場合。しかし、あなたの言うことは真実です。なぜかわからない!
Brian Knoblauch、

7
@Brian Knoblauch: "dec eax"(x86コード)などの命令を使用すると、その命令は自動的にZ(ゼロ)フラグを設定し、その間に別の比較命令を使用しなくてもすぐにテストできます。
グレッグヒューギル

1
ただし、JavaScriptを解釈するときは、オペコードがボトルネックになるとは思えません。トークンが少ないということは、インタープリターがソースコードをより速く処理できることを意味します。
Todd Owen、

6

これは、--またはに依存していません++記号に依存しませんが、ループで適用する条件に依存します。

例:変数が静的な値を持っていると、ループの長さが配列の長さなどの条件のように毎回ループチェックされる場合よりも、ループが高速になります。

しかし、今回の効果はナノ秒単位で測定されるため、この最適化について心配する必要はありません。


複数のナノ秒ループは数秒になる可能性があります...時間があれば、最適化することは決して悪い考えではありません
Buksy '31 / 10/31

6

for(var i = array.length; i--; )それほど速くはありません。しかし、で置き換えるarray.lengthsuper_puper_function()、それが大幅に速くなる場合があります(すべての反復で呼び出されるため)。それが違いです。

2014年に変更する場合は、最適化について考える必要はありません。「検索と置換」で変更する場合は、最適化について考える必要はありません。時間がない場合は、最適化について考える必要はありません。しかし、今、あなたはそれについて考える時間があります。

PS:i--よりも速くありませんi++


6

簡単に言うと、JavaScriptでこれを実行しても違いはありません。

まず、自分でテストできます。

JavaScriptライブラリのスクリプトをテストして実行できるだけでなく、以前に記述されたスクリプト全体にアクセスできるだけでなく、異なるプラットフォームの異なるブラウザーでの実行時間の違いを確認する機能も備えています。

ご覧のとおり、どの環境でもパフォーマンスに違いはありません。

スクリプトのパフォーマンスを向上させたい場合は、次のことを実行できます。

  1. 持っているvar a = array.length;あなたはループの中で、その値ごとに計算されないように文を
  2. ループ展開を行うhttp://en.wikipedia.org/wiki/Loop_unwinding

しかし、あなたが得ることができる改善はあまり重要ではないことを理解する必要があります。そのため、ほとんど気にする必要さえありません。

なぜそのような誤解(Dec vs Inc)が現れたのか私自身の意見

ずっと昔に、共通の機械命令DSZ(Decrement and Skip on Zero)がありました。アセンブリ言語でプログラミングした人々は、レジスタを保存するためにこの命令を使用してループを実装しました。さて、この古代の事実は時代遅れであり、この疑似改善を使用しても、どの言語でもパフォーマンスの改善は得られないと確信しています。

そのような知識が私たちの時代に広まる唯一の方法は、あなたが他人の人のコードを読むときだと思います。そのような構造を見て、なぜそれが実装されたのか、そしてここで答えは「ゼロに匹敵するため、パフォーマンスが向上する」と尋ねます。あなたは同僚のより高い知識に戸惑い、それをもっと賢くするためにそれを使うことを考えます:-)


興味深いですが、テストをwin7のfirefox 16.0.2で実行した場合、デクリメントループは29%遅くなりました...編集:無視してください。繰り返しのテストでは結論が出ないことが判明し、その後の実行のテスト結果に驚くほどの量のノイズがあります。理由はよくわかりません。

ええ、他のすべてを閉じてテストを実行することで、それを説明しようとしました。まだ不安定な結果が出ました。非常に奇妙な。

JavaScriptでゼロになる方が良いと考えられる本当のポイントを逃したと思います。これは主に、この方法ではループの実行を制御する変数が1つしかないためです。たとえば、この方法では、オプティマイザ/ JITerに改善の余地があります。array.lengthJS仮想マシンは、配列がループの本体によって変更されていないかどうかを判断するのに十分スマートであるという理由だけで、必ずしも使用してもパフォーマンスが低下するわけではありません。以下の私の答えを参照してください。
Pavel P

1
細心の注意:この古代の事実(アセンブリ言語の最適化)は時代遅れではなく、ただ難解です。あなたが本当に知らない限り、あなたは知る必要はありません。:-)
ハビエル

6

私が作っjsbenchの比較を

alestaniが指摘したように、昇順ループで時間がかかる1つのことは、反復ごとに配列のサイズを評価することです。このループでは:

for ( var i = 1; i <= array.length; i++ )

.length増分するたびに評価しますi。これで:

for ( var i = 1, l = array.length; i <= l; i++ )

.length宣言するときに、1回だけ評価しますi。これで:

for ( var i = array.length; i--; )

比較は暗黙的で、デクリメントの直前に行われi、コードは非常に読みやすくなっています。しかし、大きな違いを生むことができるのは、ループの内側に置くものです。

関数の呼び出しでループする(他の場所で定義):

for (i = values.length; i-- ;) {
  add( values[i] );
}

インラインコードでループする:

var sum = 0;
for ( i = values.length; i-- ;) {
  sum += values[i];
}

読みやすさを犠牲にすることなく、関数を呼び出す代わりにコードをインライン化できる場合は、ループを1桁速くすることができます。


:ブラウザーは単純な関数のインライン化が上手になっているため、コードの複雑さに依存しています。したがって、最適化する前にプロファイルを作成します。

  1. ボトルネックは他の場所にある可能性があります(ajax、リフローなど)。
  2. より良いアルゴリズムを選択できます
  3. より良いデータ構造を選択できます

でも覚えておいて:

コードは人々が読むために書かれており、付随的にマシンが実行するために書かれています。


この回答とベンチマークの+1。forEachテストを追加して、ブラウザーとノードで実行可能なスタンドアロンファイルにベンチマークを作り直しました。jsfiddle.net/hta69may/2。ノードの場合、「逆ループ、暗黙的な比較、インラインコード」が最も高速です。しかし、FF 50でのテストは奇妙な結果を示しました。タイミングがほぼ10分の1(!)であっただけでなく、両方の「forEach」テストは「逆ループ」と同じくらい高速でした。たぶんNodeの人はV8の代わりにMozillaのJSエンジンを使うべきですか?:)
Fr0sT 2017

4

あなたが今それをしている方法は速くはありません(それが不確定なループであることは別として、あなたがそうするつもりi--だったと思います。

より速くしたい場合は、次のようにします。

for (i = 10; i--;) {
    //super fast loop
}

もちろん、このような小さなループでは気付かないでしょう。それがより速い理由は、それが「true」であることを確認しながらiをデクリメントしているためです(0に達すると「false」と評価されます)。


1
セミコロンがありませんか? (i = 10; i--;)
searlea

はいはい、私は間違いを修正しました。そして、私たちがうるさい場合は、あなたのiの後にセミコロンを忘れたことを指摘します-!へえ。
djdd87 2009

なぜそれが速いのでしょうか?ソースが短いからといって、処理が速くなるわけではありません。測定しましたか?
グレッグヒューギル

4
はい、私はそれを測定しました、そして私は短いソースがそれをより速くすることを言っているわけではありません、しかしより少ない操作がそれをより速くするということです。
peirix 2009

4

コードの記述方法に非常に小さな変更を加えると、コードの実際の実行速度に大きな違いが生じることがあります。小さなコード変更が実行時間に大きな違いをもたらす可能性がある1つの領域は、配列を処理するforループがあるところです。配列がWebページの要素(ラジオボタンなど)の場合、変更が最も効果的ですが、配列がJavaScriptコードの内部にある場合でも、この変更を適用する価値はあります。

forループをコーディングして配列lisを処理する従来の方法は、次のとおりです。

for (var i = 0; i < myArray.length; i++) {...

この問題は、myArray.lengthを使用して配列の長さを評価するのに時間がかかることと、ループをコーディングした方法が意味するのは、この評価をループの周りで毎回実行する必要があることです。配列に1000個の要素が含まれている場合、配列の長さは1001回評価されます。ラジオボタンを見ていてmyForm.myButtons.lengthがあった場合、指定されたフォーム内の適切なボタンのグループを最初に見つけてからループの長さを評価する必要があるため、評価にさらに時間がかかります。

明らかに、配列の処理中に配列の長さが変化することは想定していないため、これらの長さの再計算はすべて、処理時間を不必要に追加するだけです。(もちろん、ループ内に配列エントリを追加または削除するコードがある場合、配列サイズは反復間で変わる可能性があるため、それをテストするコードを変更することはできません)

サイズが固定されているループに対してこれを修正するためにできることは、ループの開始時に一度長さを評価し、それを変数に保存することです。その後、変数をテストして、ループをいつ終了するかを決定できます。これは、特に配列に含まれているエントリが2つ以上ある場合やWebページの一部である場合に、毎回配列の長さを評価するよりもはるかに高速です。

これを行うコードは次のとおりです。

for (var i = 0, var j = myArray.length; i < j; i++) {...

したがって、配列のサイズを1回だけ評価し、ループのたびにその値を保持する変数に対してループカウンターをテストします。この追加の変数は、配列のサイズを評価するよりもはるかに速くアクセスできるため、コードは以前よりもはるかに速く実行されます。スクリプトに追加する変数は1つだけです。

多くの場合、配列内のすべてのエントリが処理される限り、配列を処理する順序は重要ではありません。この場合、追加したばかりの追加の変数を取り除き、配列を逆の順序で処理することで、コードをわずかに高速化できます。

可能な限り最も効率的な方法で配列を処理する最後のコードは次のとおりです。

for (var i = myArray.length-1; i > -1; i--) {...

このコードは、配列のサイズを最初に一度だけ評価しますが、ループカウンターを変数と比較する代わりに、定数と比較します。定数は変数よりもアクセスがより効果的であり、代入ステートメントが以前よりも1つ少ないため、コードの3番目のバージョンは2番目のバージョンよりもわずかに効率的で、最初のバージョンよりもはるかに効率的です。


4

++--JavaScriptはインタプリタ言語であり、コンパイルされた言語ではないため、vs は関係ありません。各命令は複数の機械語に変換されるので、細かいことは気にしないでください。

アセンブリ命令を効率的に使用するために--(または++)を使用することについて話している人々は間違っています。これらの命令は整数演算に適用され、JavaScriptには整数なく、数値のみです

読みやすいコードを書く必要があります。


3

多くの場合、これはプロセッサが他の比較よりも速くゼロに比較できるという事実とは本質的に何の関係もありません。

これは、少数のJavaScriptエンジン(JITリストにあるもの)のみが実際に機械語コードを生成するためです。

ほとんどのJavascriptエンジンは、ソースコードの内部表現を構築し、それらを解釈します(これがどのようなものかを理解するには、FirefoxのSpiderMonkeyのこのページの下部近くをご覧ください)。一般に、コードの一部が実質的に同じことを行うが、内部表現がより単純になる場合、より速く実行されます。

変数から変数を追加/減算する、または変数を何かと比較するなどの単純なタスクでは、インタープリターがある内部「命令」から次の「命令」に移動するオーバーヘッドが非常に高くなるため、 JSエンジンによって内部的に使用される方が良いです。


3

まあ、私はJavaScriptについては知りませんが、それは本当に配列の再評価の長さの問題であり、おそらく連想配列と関係があるはずです(デクリメントするだけの場合、新しいエントリを割り当てる必要があるとは考えられません-もし配列は密集しています。つまり、誰かがそのために最適化する可能性があります)。

低レベルのアセンブリには、DJNZ(ゼロ以外の場合はデクリメントおよびジャンプ)と呼ばれるループ命令があります。したがって、デクリメントとジャンプはすべて1つの命令で行われるため、INCおよびJL / JBよりもわずかに速くなる可能性があります(インクリメント、以下の場合はジャンプ、以下の場合はジャンプ)。また、ゼロとの比較は、別の数値と比較するよりも簡単です。しかし、これらはすべてわずかであり、ターゲットアーキテクチャにも依存します(スマートフォンのArmなどで違いが生じる可能性があります)。

この低レベルの違いがインタープリター型言語に大きな影響を与えるとは思わないでしょう。DJNZの応答を見たことがないので、興味深い考えを共有したいと思いました。


記録としてDJNZは、8051(z80)ISAの指示です。x86はのdec/jnz代わりに持っておりinc/cmp/jne、どうやらarmは同様の何かを持っています。これがJavaScriptの違いの原因ではないことは間違いありません。これは、ループ条件でevalを実行する必要があるためです。
Peter Cordes

3

これは、使用つのみ結果デクリメント値があるため(C ++で)--Iが速かったと言っします。i--は、デクリメントされた値をiに保存し、元の値を結果として保持する必要があります(j = i--;)。ほとんどのコンパイラでは、これは1つではなく2つのレジスタを使用し、レジスタ変数として保持されるのではなく、別の変数をメモリに書き込む必要が生じる可能性があります。

最近は何の違いもないと言っている他の人たちにも同意します。


3

非常に単純な言葉で

「i--とi ++。実際には、両方とも同じ時間がかかります」。

ただし、この場合、インクリメンタル操作があると..プロセッサーは、変数が1ずつインクリメントされるたびに.lengthを評価し、デクリメントの場合は..特にこの場合、0になるまで一度だけ.lengthを評価します。


3

まず、JavaScriptを含むすべてのプログラミング言語でまったく同じ時間i++i--取ります。

次のコードは、かなり時間がかかります。

速い:

for (var i = 0, len = Things.length - 1; i <= len; i++) { Things[i] };

スロー:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

したがって、次のコードも時間がかかります。

速い:

for (var i = Things.length - 1; i >= 0; i--) { Things[i] };

スロー:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

PS Slowは、コンパイラの最適化のため、一部の言語(JavaScriptエンジン)でのみ低速です。最善の方法は、'<'の代わりに '<'(または '=')を使用し、代わりに '--i'を使用することです。


3

i--またはi ++が消費する時間はそれほど多くありません。CPUアーキテクチャの奥深くに入ると、は2の補数を実行するため、++よりも高速ですが----ハードウェア内で発生するため、高速になり、++--これらの操作の大きな違いはないと見なされます。 CPUで消費される最小時間。

forループは次のように実行します:

  • 最初に変数を1回初期化します。
  • ループの第二オペランドに制約をチェック、<><=、など
  • 次に、ループを適用します。
  • ループをインクリメントし、ループはこれらのプロセスを再度スローします。

そう、

for (var i = Things.length - 1; i >= 0; i--) {
    Things[i]
}; 

最初に一度だけ配列の長さを計算しますが、これはそれほど時間はかかりませんが、

for(var i = array.length; i--; ) 

各ループで長さを計算するため、多くの時間を消費します。


var i = Things.length - 1; i >= 0; i--長さも1回計算します。
ドミトリー

1
--演算が2の補数を実行する」とはどういう意味かはわかりませんが、何かを否定することになると思います。いいえ、どのアーキテクチャでも何も無効にしません。1を引くのは1を加えるのと同じくらい簡単で、キャリーではなく借りる回路を作るだけです。
Olathe 2013

1

この種の質問に答える最善の方法は、実際に試すことです。100万回などを数えるループを設定し、それを両方の方法で行います。両方のループの時間を計り、結果を比較します。

答えはおそらくあなたが使っているブラウザに依存するでしょう。一部の結果は他の結果とは異なります。


4
それでも、なぜそれが速いのかという彼の質問には答えません。彼は、なぜそれがそのようになっているのかについての知識なしに、ベンチマークを取得するだけです...
peirix

3
それは本当だ。ただし、各ブラウザでの各JavaScriptエンジンの正確な実装を知らなければ、「なぜ」の部分に答えることはほぼ不可能です。ここでの回答の多くは、「ポストデクリメントではなくプリデクリメントを使用する」(C ++最適化トリック)や「ゼロと比較する」などの事例的な推奨事項を投げかけていますが、ネイティブコードコンパイル言語では当てはまる可能性がありますが、JavascriptはCPU。
グレッグヒューギル

4
-1最善の方法はそれを試すことだと私はまったく同意しません。いくつかの例は集合的な知識に取って代わるものではありません、そしてそれはこのようなフォーラムの全体のポイントです。
クリストフ

1

大好きです、たくさんのマークアップがありますが、答えはありません:D

単純にゼロとの比較を行うと、常に最速の比較になります

したがって、(a == 0)は(a == 5)よりもTrueを返す方が実際には高速です。

それは小さくて取るに足らないものであり、コレクションに1億行あるため、測定可能です。

つまり、ループアップでは、i <= array.lengthであり、iをインクリメントしていると言っているかもしれません。

ダウンループでは、i> = 0と言って、代わりにiをデクリメントする場合があります。

比較はより速くなります。ループの「方向」ではありません。


Javascriptエンジンはすべて異なり、答えは測定対象のブラウザーに正確に依存するため、前述の質問に対する答えはありません。
グレッグヒューギル

いいえ、根本的に受け入れられているゼロとの比較が最速です。あなたの発言も正しいですが、ゼロと比較するという黄金律は絶対的です。
ロバート

4
これは、コンパイラがその最適化を行うことを選択した場合にのみ当てはまります。これは確かに保証されていません。定数の値を除いて、コンパイラーは(a == 0)と(a == 5)でまったく同じコードを生成する可能性があります。比較の両側が変数である場合(CPUの観点から)、CPUは他の値よりも速く0と比較しません。一般に、ネイティブコードコンパイラのみがこのレベルで最適化する機会があります。
グレッグヒューギル

1

他の人を助けて頭痛を避けよう---これに投票してください!!!

このページで最も人気のある回答はFirefox 14では機能せず、jsLinterを通過しません。「while」ループには、代入ではなく比較演算子が必要です。クロム、サファリ、さらにはIEでも機能します。しかし、Firefoxで死にます。

これは壊れています!

var i = arr.length; //or 10
while(i--)
{
  //...
}

これはうまくいきます!(Firefoxで動作し、jsLinterを渡します)

var i = arr.length; //or 10
while(i>-1)
{
  //...
  i = i - 1;
}

2
私はFirefox 14で試したところ、うまくいきました。を使用しwhile (i--)、多くのブラウザーで機能する実稼働ライブラリーの例を見てきました。あなたのテストについて奇妙なことがありましたか?Firefox 14のベータ版を使用していましたか?
Matt Browne 2013年

1

これは単なる推測ですが、プロセッサが何かを別の値(i <Things.length)と比較するのではなく0(i> = 0)と比較する方が簡単だからです。


3
+1他の人が反対票を投じないでください。.lengthを繰り返し評価することは、実際のinc / decrementよりもはるかに問題ですが、ループの終了を確認することが重要になる場合があります。私のCコンパイラは、様ループ上のいくつかの警告を与える:「リマーク#1544-D:(ULP 13.1)カウントアップループ検出ループがゼロを検出することが容易であるとカウントダウンオススメ。」
ハーパーは、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.