2009-11-01 10 views
11

A)for(int i = 100000; i > 0; i--) {}これらのコードのうちJavaで高速なものはどれですか?

B)for(int i = 1; i < 100001; i++) {}

答えがthis website(質問3)の上にあります。私はちょうど把握できないなぜですか?ウェブサイトから

3.多くのコンパイラで

+11

実際に、最初のバージョンが実際に高速であることを確認しましたか?私はむしろそれが疑わしいからです。 –

+2

いくつかの質問は、使用される英語の品質が低いため、読みにくく理解することができません。 –

+9

このインタビューの質問のリストから抜けてください:*これらのすべてに答えると、まだここで働きたいですか?*答えは1つだけです。 –

答えて

66

私が使用しますそれは一対一主に)マッピングするためのアセンブリは、()は、例えば0と50にインクリメントいずれかに減分空ループの差が線に沿ってあることが多い。

 ld a,50    ld a,0 
loop: dec a    loop: inc a 
     jnz loop    cmp a,50 
           jnz loop 

でゼロフラグためですすなわちほとんどの正常なCPUは、ゼロに達すると減分命令によって設定されます。通常、インクリメント命令が50に達したときにインクリメント命令について同じことが言えるわけではありません(ゼロとは異なり、その値について特別なことはないので)。したがって、レジスタを50と比較してゼロフラグを設定する必要があります。


はしかし、2つのループのどれを尋ねる:

for(int i = 100000; i > 0; i--) {} 
for(int i = 1; i < 100001; i++) {} 

は高速です(そうでない場合は、かなりのいずれかの環境、Javaまたは)それらのどちらも有益な何かを行いますので、無用です。最速これらのループのバージョンは全くループしません。私は誰よりも速いバージョンを思い付くように挑戦します:-)

これらは、中括弧の中で便利な作業を開始するときにのみ役に立ちます。その時点で、の作業は、使用する必要があります。

たとえば、には、が1から100,000までカウントする必要がある場合は、2番目のループを使用する必要があります。それは、あなたがそれを使用する必要があるたびに、ループ内で100000-iを評価しなければならないという事実によって、カウントダウンの利点(もしあれば)が圧倒される可能性があるからです。

 ld b,100000    dsw a 
    sub b,a 
    dsw b 

dswは、もちろん、悪名高いdo something withアセンブラニーモニックである):アセンブリ点で、それは差であろう。

あなただけのイテレーションごとに一度インクリメントループのためのヒットを取ることでしょう、そしてあなたは(あなたがiを使うことになると仮定すると減算少なくとも反復ごとに一度のヒットを取ることになりますので、そうでない場合がありますループの必要性はほとんどありません)、より自然なバージョンにしてください。

カウントアップする必要がある場合は、カウントアップしてください。カウントダウンする必要がある場合は、カウントダウンします。

+18

'dswを読むとすぐに+1しました –

+0

良いアドバイス。また、ブランチ予測では、アセンブリ命令のカウントアップとカウントダウンではパフォーマンスの差はごくわずかになります(しかし、このようなマイクロ最適化はソースコードを汚染する価値がないことに同意します)。質問に答えることが一切ないので、 –

+2

-1。具体的に言うと、「Java」です。 VMのいくつの層がその間に置かれているかを考えると、マシンコードでは何が起こるのかは関係ありません。 –

23

、ループが後方に行くために放出されたマシン命令は、より効率的であるため、ゼロのテスト(従ってレジスタをゼロにすることは、一定値のロード即値よりも速い。

一方、優れた最適化コンパイラが内部ループを検査し、後方に行くことの任意の副作用を引き起こすことはありませんことを決定することができるはず...

ところで、それは私の中にひどい面接の質問です意見。あなたが10百万回実行されるループについて話していない限り、フォワードループ値(n - i)を再現する多くの場合よりわずかなゲインが上回らないことを確認していれば、パフォーマンスの向上は最小限に抑えられます。

常にと同じように、パフォーマンスベンチマークを行わずにコードを理解しにくくすることでマイクロ最適化しないでください。

+12

ええ、このようなマイクロ最適化では、CやC++ではほんの少しの妥当性がありますが、Javaでは有効ではありません。 –

+4

これは当てはまりますが、パフォーマンスの向上はそれほど重要ではないので、努力する価値はありません。誰かが私にパフォーマンスの向上のために減るforループを使用するべきだと言ったら、彼らはあまりにも頑張っているので、これはひどいインタビューの質問であると私は同意する。 –

0

答えは、私は理由がループを終了させるためのi > 0条件をテストするために高速であるということだと思います(おそらく、ウェブサイト上で見られるような)

です。

0

パフォーマンス上重要でないアプリケーションでは、その違いはおそらく無関係です。他の人が指摘しているように、++の代わりに++を使うともっと速くなりますが、特にforループでは現代のコンパイラがその区別を最適化する必要があります。

しかし、違いはおそらく比較のために生成される基本的な命令と関係があります。値が0に等しいかどうかのテストは、単に NAND NORゲートです。値が任意の定数と等しいかどうかをテストするには、その定数をレジスタにロードし、次に2つのレジスタを比較する必要があります。 (これはたぶん余分なゲート遅延または2を必要とするでしょう)。言い換えれば、パイプライニングと最新のALUでは、区別が重要であれば驚くでしょう。

+0

"値が0に等しいかどうかのテストは単にNANDゲートです。" - 1つのNANDゲートでは十分ではありません!実際には、ゼロのテストはほとんどのプロセッサにハードワイヤード接続されています。 x86上の演算命令は、演算の結果がゼロの場合にゼロフラグを設定します。これは、比較命令が不要であることを意味します。 – Artelius

+0

申し訳ありませんが、私はNANDではなくNORを意味しました。それは、1つのNORゲート(十分な入力が与えられている)が不十分なのはなぜですか?すべての入力が0の場合、NORは1を返します。 –

+0

32入力NORゲートは実用的ではないと思います。おそらく、ある種の連鎖が有線システムに使われるでしょう。しかし、現代のプロセッサでは、これはおそらくマイクロコードを使って行われます。 – Artelius

17

これらの種類の質問は、大部分が無関係な気晴らしで、一部の人々はそれに取りつかれています。 マイクロ最適化のカルトとか好きなものを呼んでくださいが、ループアップやループが速いのですか?真剣に?あなたはあなたがしていることにふさわしいものを使用します。あなたは、2クロックサイクルまたはそれが何であれ保存することについてコードを書いていません。

コンパイラには何をするのかを指示し、インテントをクリアします(コンパイラとリーダーの両方に)。過度の連結は、メモリフラグメンテーションを生じないため

public final static String BLAH = new StringBuilder().append("This is ").append(3).append(' text").toString(); 

なく定数をコンパイラが(となります)、この最適化することができる:別の共通のJava pessimizationは、それが最初に最適化しないであろう

public final static String BLAH = "This is a " + 3 + " test"; 

を2番目は読みやすくなります。

(a>b)?a:bMath.max(a,b)?私はむしろ最初の関数呼び出しオーバーヘッドが発生しないことを気にしないので、私はむしろ2番目を読むだろう知っている。

finallyブロックがSystem.exit()で呼び出されていないことを知ってのように、このリストに有用なもののカップルが有用潜在であります。 floatを0.0で除算しても例外がスローされないことがわかっていると便利です。

しかし、第二推測それ本当に事項(と私は時間の99.99%がそうでないことを賭ける)しない限り、コンパイラは気にしないでください。

+1

... Gentooでは、私はアプリケーションの 'for'ループを魔法のように逆転させるUSEフラグを持っており、それはGHzあたり218 ipsの赤ちゃんを得ます。 –

+0

Math.max(..)のことは本当ですか? IIRCでは、JVMは通常、Math *の多くを最適化します。メソッドコールなどではなく、直接コードに変換します。 つまり、Math.max()は実際に実装されています。まともなJVM/javacの組み合わせでも同じです。 – Adam

+1

@Adam:Math.max()が遅いと主張するリンク先のサイトを見ると、これは、関数呼び出しのオーバーヘッド、ボクシング/アンボクシング(プリミティブ型のmax()バージョンがあるので、実際にはそうであるかどうかはわかりません)またはその両方のためです。どのような場合でも、マイクロ最適化です。 – cletus

1

ループが1つの重要な部分を除いて、同一です:

I> 0; および i <100001;

0より大きいチェックは、コンピュータのNZP(一般に状態コードまたは負のゼロまたは正のビットとして知られている)ビットをチェックすることによって行われます。

NZPビットは、ロード、AND、加算などの操作が行われるたびに設定されます。が実行される。

これ以上のチェックでは、このビットを直接利用することはできません(したがって、少し時間がかかります...)一般的な解決策は、値の1つを負にすることです(ビット単位でNOTを行い、比較された値に変換する。結果がゼロの場合、それらは等しい。正の場合、2番目の値(負ではない)が大きくなります。負の場合、最初の値(neg)は大きくなります。このチェックには、直接nzpチェックよりも少し時間がかかります。

私は、これはその背後にある理由は、しかしであることを100%確実ではないんだけど、それが可能な理由のように思える...あなたは最低レベル(マシンコードに取り掛かるが、とき

2

JVMでゼロをテストする場合は、ifeqで実行できますが、何か他のテストではif_icmpeqが必要ですが、スタックに余分な値を入れる必要があります。

> 0のテストは、ifgtで行われることがありますが、< 100001のテストでは、if_icmpltが必要です。

+1

これは、JVMがバイトコードを解釈している間にのみ適切です。ネイティブコードに最適化されていれば、違いはなく、空のループの場合は何も置き換えられません。 –

+0

ネイティブコードでも、ほとんどの(?)アーキテクチャは、ゼロと比較する命令と、他のすべてのものと比較する1つまたは2つの他の方法を持っています。理論的には、たとえあなたが間違ったやりかたを数えているからといってループ内で他の愚かな "トリック"をしなくてはならないという違いがあると思っても、おそらく違いはあります。典型的なマイクロ最適化。 – Fredrik

+0

@Fredrik:ほとんどのアーキテクチャでは、増分/減分を実行しながらゼロをテストできます。だから、あなたは比較命令を全く必要としません。 x86では算術命令の一部として「ゼロフラグ」が更新されますが、ARMでは特定の算術命令でフラグを更新するかどうかを指定できます。しかし、これは、より良いパイプライン化およびスーパースカラ演算のために、従来のものよりはるかに小さい効果を有する。 – Artelius

10

最新のJava実装では、これは当てはまりません。時間差が脆く、どこかのループの近くに小さな変化がそれらを好転させることができること

 
Java(TM) SE Runtime Environment 1.6.0_05-b13 
Java HotSpot(TM) Server VM 10.0-b19 
up 1000000000: 1817ms 1.817ns/iteration (sum 499999999500000000) 
up 1000000000: 1786ms 1.786ns/iteration (sum 499999999500000000) 
up 1000000000: 1778ms 1.778ns/iteration (sum 499999999500000000) 
up 1000000000: 1769ms 1.769ns/iteration (sum 499999999500000000) 
up 1000000000: 1769ms 1.769ns/iteration (sum 499999999500000000) 
up 1000000000: 1766ms 1.766ns/iteration (sum 499999999500000000) 
up 1000000000: 1776ms 1.776ns/iteration (sum 499999999500000000) 
up 1000000000: 1768ms 1.768ns/iteration (sum 499999999500000000) 
up 1000000000: 1771ms 1.771ns/iteration (sum 499999999500000000) 
up 1000000000: 1768ms 1.768ns/iteration (sum 499999999500000000) 
down 1000000000: 1847ms 1.847ns/iteration (sum 499999999500000000) 
down 1000000000: 1842ms 1.842ns/iteration (sum 499999999500000000) 
down 1000000000: 1838ms 1.838ns/iteration (sum 499999999500000000) 
down 1000000000: 1832ms 1.832ns/iteration (sum 499999999500000000) 
down 1000000000: 1842ms 1.842ns/iteration (sum 499999999500000000) 
down 1000000000: 1838ms 1.838ns/iteration (sum 499999999500000000) 
down 1000000000: 1838ms 1.838ns/iteration (sum 499999999500000000) 
down 1000000000: 1847ms 1.847ns/iteration (sum 499999999500000000) 
down 1000000000: 1839ms 1.839ns/iteration (sum 499999999500000000) 
down 1000000000: 1838ms 1.838ns/iteration (sum 499999999500000000) 

注: はベンチマークとして10億までの数字を合計します。

編集: ベンチマークループはint型の合計を使用して

 long sum = 0; 
     for (int i = 0; i < limit; i++) 
     { 
      sum += i; 
     } 

 long sum = 0; 
     for (int i = limit - 1; i >= 0; i--) 
     { 
      sum += i; 
     } 

あるが約3倍高速であるが、その後、合計がオーバーフローします。 はBigIntegerので、それは50倍以上遅い:

BigInteger up 1000000000: 105943ms 105.943ns/iteration (sum 499999999500000000) 
+3

+1。ベンチマークを実行するためのものです。 –

+0

+1。ニースカウンター。 –

+0

したがって、 "sum 499999999500000000"を計算するには、longまたはBigIntegersを使用しましたか?特に、後者は非常にオーバーヘッドがあり、異なるループを揺るがすことになります。範囲の上端から始めると、数値が非常に早く大きくなり、BigIntegersを追加する速度がサイズに依存するため、これは非常に不公平なテストになります。パフォーマンスについてどちらの方法も主張していないことに注意してください。ベンチマークは、メソッドを詳述しないと有用ではないと言っているだけなので、バイアスを調べて結果を再現できる人もいます。 – Artelius

2

これは私が今まで見た中で非常識な質問についてです。ループ本体は空です。コンパイラが何か良いことがあれば、まったくコードを発行しません。それは何もしません、例外をスローすることはできませんし、その範囲外のものは変更しません。

コンパイラがスマートでないか、実際には空のループボディがないと仮定します。 "後方ループカウンタ"引数は、いくつかのアセンブリ言語に意味があります(Javaバイトコードあまりにも、私はそれを具体的に知らない)。しかし、コンパイラは非常に多くの場合、減分カウンタを使用するようにループを変換する機能を持つことになります。 iの値が明示的に使用されるループ本体がない限り、コンパイラはこの変換を実行できます。だから、あなたはしばしば違いは見られません。

13

よくある質問です。

どちらがわかりやすく、使いやすいですか?

これは、想定されるパフォーマンスの違いよりもはるかに重要です。個人的には、パフォーマンスをここでの違いを判断する基準にすべきではないということを指摘したいと思います。彼らがこれについて私の前提に挑戦したくないのであれば、私は仕事をしないことに不満はありません。 ;)

+1

これは最高の答えです。 – Sam152

3

このような質問をするインタビュアーは、ストレート回答(「ナンバーワンが速い」または「ナンバー2が速い」)を期待しているか、この質問には人々がここで与える答えに起きているのだろうか?

一般に、Javaコンパイラ、JRE、CPUなどに大きく依存するため、どちらが高速であるかはわかりません。プログラムの中でどちらか一方を使用するのは、2つのうちの1つが詳細を理解せずにより速くなると考えるからです。superstitious programmingです。あなたの特定の環境で、あるバージョンのバージョンが他のバージョンよりも速い場合でも、その差はそれほど大きくなくてもかまいません。

賢いようにしようとするのではなく、明確なコードを書いてください。

+0

引用したページでは、著者は、2番目の方が高速であると言っており、理由はありません。したがって、質問。 – rball

6

通常、実際のコードはより高速にカウントされます。これにはいくつかの理由があります。

  • プロセッサはメモリ転送を読み込むために最適化されています。
  • HotSpot(およびおそらく他のバイトコード - >ネイティブコンパイラ)は、順方向ループを大幅に最適化しますが、あまり頻繁に起こらないため逆方向ループを気にしません。
  • 通常、上向きの方が一般的です。よりクリーンなコードは、しばしば高速です。

幸いなことに、幸いなことに、通常はもっと速くなります。不要なマイクロ最適化は悪いです。私は意図的に6502アセンブラをプログラミングして以来、逆ループを書いていません。

3

このような質問は、古いベストプラクティスの推奨事項に基づいています。 これはすべての比較です.0と比較すると高速になることが知られています。数年前、これは非常に重要と考えられていたかもしれません。今日、特にJavaでは、コンパイラとVMが仕事をするようにしたいと思っています。私は、維持して理解しやすいコードを書くことに焦点を当てています。

それ以外の理由がない限り、 Javaアプリは常にHotSpotや高速ハードウェアで動作するとは限りません。

6

実際にこの質問に答える方法は2通りあります。

  1. これは本当に問題ではないことを伝えるために、あなたは自分の時間を無駄にしています。

  2. 唯一の知っている方法は、実際の運用ハードウェア、OS、およびJREのインストールで信頼できるベンチマークを実行することです。まだ

    http://code.google.com/p/caliper/source/browse/trunk/test/examples/LoopingBackwardsBenchmark.java

    このキャリパーフレームワークは、プライムタイムのために実際に準備ができていないので、それができない場合があります

だから、私はあなたがここにそれを試してみるために使用することができ、実行可能なベンチマークを作りましたあなたが本当に気にしていれば、それを理解することができます。ここに私のLinuxのボックスに与えた結果は次のとおりです。

 max benchmark  ns 
     2 Forwards   4 
     2 Backwards   3 
     20 Forwards   9 
     20 Backwards  20 
    2000 Forwards  1007 
    2000 Backwards  1011 
20000000 Forwards 9757363 
20000000 Backwards 10303707 

誰にでも逆戻りしているようですか?

+1

まあまあ、2回だけループするとどうなりますか?あなたが3人のような吸盤を持っていたら、3nsを節約できます。 3フリーキンナノ秒男!あなたはちょうど私が推測するほどハードコアです。はい、私は冗談です。 – rball

+0

リンクが壊れました。 –

+0

が修正されました。 88888888 –

2

私はスレッドを噛んでネクロバックすることにしました。

両方のループはJVMによってno-opsとして無視されます。ループの本質的に1つは10まで、もう1つは10000000までは違いはなかったでしょう。

0にループバックすることは別のことです(jne命令の場合はもう一度、それはそのようにコンパイルされません)。リンクされたサイトは奇妙で(間違っています)。

このタイプの質問は、JVM(最適化できる他のコンパイラも)に適合しません。

0

私は約15分間テストをしています。ただの場合のためにEclipse以外のものは何も実行していません。本当の違いがありました。試してみることができます。

私は、Javaが「何もしない」ためにどれくらいの時間がかかるか試してみると、アイデアを出すのに約500ナノ秒かかりました。

for(i=0;i<100;i++){}

そして、5分後に、私は "後方" いずれかを試してみました:

for(i=100;i>0;i--)

は、その後、私はそれが増加forステートメントを実行するためにかかる時間テストそして、私は第1と第2の文の間で16%の大きな違いを得ました(後者は16%高速です)。

2000のテスト中に "増加" forステートメントを実行するための

平均時間:1838 2000のテスト中に "減少" forステートメントを実行するためのn/sの

平均時間:1555 N/sの

このようなテストのために使用さ

コード:

public static void main(String[] args) { 
    long time = 0; 
    for(int j=0; j<100; j++){ 
    long startTime = System.nanoTime(); 
    int i; 
     /*for(i=0;i<100;i++){ 

     }*/ 
     for(i=100;i>0;i--){ 

     } 
    long endTime = System.nanoTime(); 
    time += ((endTime-startTime)); 
    } 
    time = time/100; 
    System.out.print("Time: "+time); 
} 

結論: 違いは基本的には何もありません。forステートメントテストに関しては、「何もしない」という意味でかなりの量の「無」が必要です。その違いは無視できます。ライブラリをインポートするのにかかる時間例えば、java.util。スキャナは、forステートメントを実行するよりも読み込みに時間がかかりますが、アプリケーションのパフォーマンスが大幅に向上することはありませんが、まだ分かりません。

関連する問題