39

先生がこれを落とすのを一度聞いたことがありますが、それ以来ずっと悩まされています。x整数が 0 以上かどうかを確認したいとしましょう。これを確認するには 2 つの方法があります。

if (x > -1){
    //do stuff
}

if (x >= 0){
    //do stuff
} 

この先生によると、その>時は少し速いだろう>=。この場合は Java でしたが、彼によると、これは C、c++、およびその他の言語にも当てはまりました。この声明に真実はありますか?

4

11 に答える 11

30

実世界の意味で違いはありません。

さまざまなターゲット用にさまざまなコンパイラによって生成されたコードを見てみましょう。

  • 私はsignedint演算(OPの意図のようです)を想定しています
  • 私は調査によって、Cと手元にあるコンパイラー(確かにかなり小さなサンプル-GCC、MSVC、IAR)に限定しました。
  • 基本的な最適化が有効になっ-O2ている(GCC、/OxMSVC、-OhIARの場合)
  • 次のモジュールを使用します。

    void my_puts(char const* s);
    
    void cmp_gt(int x) 
    {
        if (x > -1) {
            my_puts("non-negative");
        }
        else {
            my_puts("negative");
        }
    }
    
    void cmp_gte(int x) 
    {
        if (x >= 0) {
            my_puts("non-negative");
        }
        else {
            my_puts("negative");
        }
    }
    

そして、比較操作のためにそれぞれが生成したものは次のとおりです。

ARMを対象とするMSVC11:

// if (x > -1) {...
00000        |cmp_gt| PROC
  00000 f1b0 3fff    cmp         r0,#0xFFFFFFFF
  00004 dd05         ble         |$LN2@cmp_gt|


// if (x >= 0) {...
  00024      |cmp_gte| PROC
  00024 2800         cmp         r0,#0
  00026 db05         blt         |$LN2@cmp_gte|

x64を対象とするMSVC11:

// if (x > -1) {...
cmp_gt  PROC
  00000 83 f9 ff     cmp     ecx, -1
  00003 48 8d 0d 00 00                  // speculative load of argument to my_puts()
    00 00        lea     rcx, OFFSET FLAT:$SG1359
  0000a 7f 07        jg  SHORT $LN5@cmp_gt

// if (x >= 0) {...
cmp_gte PROC
  00000 85 c9        test    ecx, ecx
  00002 48 8d 0d 00 00                  // speculative load of argument to my_puts()
    00 00        lea     rcx, OFFSET FLAT:$SG1367
  00009 79 07        jns     SHORT $LN5@cmp_gte

x86をターゲットとするMSVC11:

// if (x > -1) {...
_cmp_gt PROC
  00000 83 7c 24 04 ff   cmp     DWORD PTR _x$[esp-4], -1
  00005 7e 0d        jle     SHORT $LN2@cmp_gt


// if (x >= 0) {...
_cmp_gte PROC
  00000 83 7c 24 04 00   cmp     DWORD PTR _x$[esp-4], 0
  00005 7c 0d        jl  SHORT $LN2@cmp_gte

x64をターゲットとするGCC4.6.1

// if (x > -1) {...
cmp_gt:
    .seh_endprologue
    test    ecx, ecx
    js  .L2

// if (x >= 0) {...
cmp_gte:
    .seh_endprologue
    test    ecx, ecx
    js  .L5

x86をターゲットとするGCC4.6.1:

// if (x > -1) {...
_cmp_gt:
    mov eax, DWORD PTR [esp+4]
    test    eax, eax
    js  L2

// if (x >= 0) {...
_cmp_gte:
    mov edx, DWORD PTR [esp+4]
    test    edx, edx
    js  L5

ARMをターゲットとするGCC4.4.1:

// if (x > -1) {...
cmp_gt:
    .fnstart
.LFB0:
    cmp r0, #0
    blt .L8

// if (x >= 0) {...
cmp_gte:
    .fnstart
.LFB1:
    cmp r0, #0
    blt .L2

ARMCortex-M3を対象とするIAR5.20:

// if (x > -1) {...
cmp_gt:
80B5 PUSH     {R7,LR}
.... LDR.N    R1,??DataTable1  ;; `?<Constant "non-negative">`
0028 CMP      R0,#+0
01D4 BMI.N    ??cmp_gt_0

// if (x >= 0) {...
cmp_gte:
 80B5 PUSH     {R7,LR}
 .... LDR.N    R1,??DataTable1  ;; `?<Constant "non-negative">`
 0028 CMP      R0,#+0
 01D4 BMI.N    ??cmp_gte_0

あなたがまだ私と一緒にいるなら、ここに評価(x > -1)(x >= 0)それが現れる間のメモの違いがあります:

  • ARMをターゲットとするMSVCは、 vsforを使用cmp r0,#0xFFFFFFFFします。最初の命令のオペコードは2バイト長くなっています。それはもう少し時間がかかるかもしれないと思うので、これを利点と呼びます(x > -1)cmp r0,#0(x >= 0)(x >= 0)
  • x86をターゲットとするMSVCは、 vsforを使用cmp ecx, -1します。最初の命令のオペコードは1バイト長くなっています。それはもう少し時間がかかるかもしれないと思うので、これを利点と呼びます(x > -1)test ecx, ecx(x >= 0)(x >= 0)

GCCとIARは、2種類の比較のために同一のマシンコードを生成したことに注意してください(どちらのレジスタが使用されたかを除いて)。したがって、この調査によると、(x >= 0)「より速く」なる可能性は非常に低いようです。しかし、最小限に短いオペコードバイトエンコーディングが持つ可能性のある(そして私が強調するかもしれない)利点が何であれ、他の要因によって確かに完全に影が薄くなります。

JavaまたはC#のjitted出力に何か違うものを見つけたら、私は驚きます。8ビットAVRのような非常に小さなターゲットでも、注意の違いが見つかるとは思えません。

つまり、このマイクロ最適化について心配する必要はありません。ここでの私の書き込みは、私の生涯でそれらを実行するすべてのCPUにわたって蓄積されたこれらの式のパフォーマンスの違いによって費やされるよりも、すでに多くの時間を費やしていると思います。性能の違いを測定できる方は、亜原子粒子の振る舞いなど、もっと重要なことに力を入れてください。

于 2013-01-25T21:03:28.737 に答える
30

基盤となるアーキテクチャに大きく依存しますが、違いはごくわずかです。

(x >= 0)どちらかといえば、一部の命令セット (ARM など)では との比較が0無料で行われるため、わずかに高速になると予想されます。

もちろん、賢明なコンパイラは、ソースに含まれるバリアントに関係なく、最適な実装を選択します。

于 2013-01-25T11:27:43.910 に答える
20

あなたの先生は本当に古い本を何冊か読んでいます。以前は、評価に必要なマシンサイクルがより少ないというgreater than or equal指示がない一部のアーキテクチャの場合でしたが、これらのプラットフォームは最近ではまれです。読みやすくするために、を使用することをお勧めします。>>=>= 0

于 2013-01-25T11:29:41.490 に答える
14

ここでのより大きな懸念は、時期尚早の最適化です。多くの人は、効率的なコードを書くことよりも、読みやすいコードを書くこと方が重要だ考えています [ 1、2 ]。設計が機能することが証明されたら、これらの最適化を低レベル ライブラリの最終段階として適用します。

読みやすさを犠牲にして、コードをごくわずかに最適化することを常に考えるべきではありません。コードの読み取りと保守が難しくなるためです。これらの最適化を行う必要がある場合は、それらを低レベルの関数に抽象化して、人間にとって読みやすいコードが残るようにします。

クレイジーな例として、アセンブリでプログラムを書いている人から、余分な効率を犠牲にして、デザイン、使いやすさ、および保守性の面で Java を使用することを厭わない人を考えてみてください。

余談ですが、C を使用している場合は、分散操作よりも効率性、可読性、および保守性が向上するため、わずかに効率的なコードを使用するマクロを作成する方が実行可能なソリューションになる可能性があります。

もちろん、効率と読みやすさのトレードオフはアプリケーションによって異なります。そのループが 1 秒間に 10000 回実行されている場合、それはボトルネックである可能性があり、最適化に時間を費やすことをお勧めします。

于 2013-01-25T11:36:41.283 に答える
9

はい、違いがあります。バイトコードが表示されるはずです。

為に

if (x >= 0) {}

バイトコードは

ILOAD 1
IFLT L1

為に

if (x > -1) {}

バイトコードは

ILOAD 1
ICONST_M1
IF_ICMPLE L3

バージョン 1 は、特殊なゼロ オペランド演算を使用するため高速です

iflt : jump if less than zero 

ただし、解釈専用モードjava -Xint ...で JVM を実行している場合にのみ違いを見ることができます。たとえば、このテスト

int n = 0;       
for (;;) {
    long t0 = System.currentTimeMillis();
    int j = 0;
    for (int i = 100000000; i >= n; i--) {
        j++;
    }
    System.out.println(System.currentTimeMillis() - t0);
}

n = 0 の場合は 690 ミリ秒、n = 1 の場合は 760 ミリ秒を示しています。

于 2013-01-25T11:35:06.143 に答える
4

実際、2 番目のバージョンは 1 ビットのチェックが必要なため、わずかに高速になるはずです (上記のようにゼロで比較すると仮定します)。ただし、ほとんどのコンパイラはそのような呼び出しを最適化するため、そのような最適化が実際に表示されることはありません。

于 2013-01-25T11:27:26.127 に答える
3

">=" は ">" と同様に単一操作です。OR を使用した 2 つの個別の操作ではありません。

ただし、コンピュータは 1 ビット (負符号) のみをチェックする必要があるため、>=0 の方がおそらく高速です。

于 2013-01-25T11:35:15.703 に答える
1

この先生によると、 > は > = よりもわずかに高速です。この場合は Java でしたが、彼によると、これは C、c++、およびその他の言語にも当てはまりました。この声明に真実はありますか?

あなたの先生は根本的に間違っています。0 との比較がわずかに高速である可能性があるだけでなく、この種のローカル最適化がコンパイラ/インタープリターによって適切に行われ、助けようとするすべてを台無しにすることができるためです。教えることは絶対に良くない。

あなたは読むことができます: これまたはこれ

于 2013-01-25T12:12:33.337 に答える
1

パフォーマンスに関するこの会話に割り込んで申し訳ありません。

脱線する前に、JVM には、ゼロだけでなく定数 1 ~ 3 を処理するための特別な指示があることに注意してください。そうは言っても、ゼロを処理するアーキテクチャの機能は、コンパイラの最適化だけでなく、バ​​イトコードから機械コードへの変換などの背後で長い間失われている可能性があります。

jax86 アセンブラー言語の時代から、セットにはより大きい ( ) とより大きいか等しい( )の両方の命令があったことを覚えていますjae。次のいずれかを行います。

; x >= 0
mov ax, [x]
mov bx, 0
cmp ax, bx
jae above

; x > -1
mov ax, [x]
mov bx, -1
cmp ax, bx
ja  above

命令が同一または類似しているため、これらの選択肢は同じ時間かかり、予測可能な数のクロック サイクルを消費します。たとえば、これを参照してください。ja実際にjaeは異なる数の算術レジスタをチェックする場合がありますが、そのチェックは、命令が予測可能な時間を要する必要性によって支配されます。これは、CPU アーキテクチャを管理しやすくするために必要です。

しかし、私は脱線するためにここに来ました。

私の前にある答えは適切である傾向があり、どちらのアプローチを選択しても、パフォーマンスに関する限り、同じ球場にいることを示しています.

これにより、他の基準に基づいて選択する必要があります。で、ここはメモしておきたいところです。インデックスをテストするときは、厳密にバインドされたスタイル チェック、主x >= lowerBoundに を優先しx > lowerBound - 1ます。議論は不自然であるに違いありませんが、ここでは他のすべてが真に等しいため、読みやすさに要約されます。

概念的には下限に対してテストしているためx >= lowerBound、コードの読者から最も適応した認識を引き出す正規のテストです。x + 10 > lowerBound + 9x - lowerBound >= 0、およびx > -1はすべて、下限に対してテストする迂回方法です。

繰り返しますが、割り込んで申し訳ありませんが、これは物事の学問を超えて重要だと感じました。私は常にこれらの用語で考え、定数や演算子の厳密さをいじることから抜け出すことができると考える微妙な最適化についてコンパイラーに心配させます。

于 2013-01-25T19:49:47.193 に答える
0

まず第一に、それはハードウェアプラットフォームに大きく依存します。最近のPCとARMSoCの違いは、主にコンパイラの最適化に依存しています。しかし、FPUのないCPUの場合、署名された計算は大惨事になります。

たとえば、Intel 8008、8048、8051、Zilog Z80、Motorola 6800、または最新のRISC PICやAtmelマイクロコントローラーなどの単純な8ビットCPUは、8ビットレジスタを備えたALUを介してすべての計算を行い、基本的にキャリーフラグビットとz(ゼロ値インジケータ)フラグビット。すべての真面目な数学は、ライブラリと式を介して行われます

  BYTE x;
  if (x >= 0) 

非常にコストのかかるライブラリ呼び出しなしでJZまたはJNZasm命令を使用すると、間違いなく勝ちます。

于 2013-01-25T23:13:55.033 に答える