75

!=と==は、ゼロまたはゼロ以外をテストするための最速の方法ではないことがわかりました。

bool nonZero1 = integer != 0;
xor eax, eax
test ecx, ecx
setne al

bool nonZero2 = integer < 0 || integer > 0;
test ecx, ecx
setne al

bool zero1 = integer == 0;
xor eax, eax
test ecx, ecx
sete al

bool zero2 = !(integer < 0 || integer > 0);
test ecx, ecx
sete al

コンパイラ:VC ++ 11最適化フラグ:/ O2 / GL / LTCG

これは、x86-32のアセンブリ出力です。両方の比較の2番目のバージョンは、x86-32とx86-64の両方で約12%高速でした。ただし、x86-64では、手順は同じでした(最初のバージョンは、2番目のバージョンとまったく同じように見えました)が、2番目のバージョンの方が高速でした。

  1. コンパイラがx86-32でより高速なバージョンを生成しないのはなぜですか?
  2. アセンブリ出力が同じであるのに、x86-64で2番目のバージョンがさらに高速なのはなぜですか?

編集:ベンチマークコードを追加しました。ZERO:1544ms、1358ms NON_ZERO:1544ms、1358mshttp: //pastebin.com/m7ZSUrcP または http://anonymouse.org/cgi-bin/anon-www.cgi/http://pastebin.com/m7ZSUrcP

注:main.asmは非常に大きくなるため、単一のソースファイルにコンパイルするときにこれらの関数を見つけるのはおそらく不便です。別のソースファイルにzero1、zero2、nonZero1、nonZero2がありました。

EDIT2:VC++11とVC++2010の両方がインストールされている人がベンチマークコードを実行してタイミングを投稿できますか?それは確かにVC++11のバグかもしれません。

4

2 に答える 2

122

これは素晴らしい質問ですが、コンパイラの依存関係分析の犠牲になっていると思います。

コンパイラーはeax1回の上位ビットをクリアするだけでよく、2番目のバージョンでもクリアされたままになります。xor eax, eax2番目のバージョンは、コンパイラの分析によって最初のバージョンによってクリアされたままになっていることが証明されたことを除いて、代償を払う必要があります。

2番目のバージョンは、コンパイラが最初のバージョンで行った作業を利用して「チート」することができます。

時間をどのように測定していますか?「(バージョン1、続いてバージョン2)がループ内にある」、または「(バージョン1がループ内にある)後に(バージョン2がループ内にある)」ですか?

同じプログラムで両方のテストを実行しないでください(バージョンごとに再コンパイルする代わりに)。実行する場合は、「バージョンAファースト」と「バージョンBファースト」の両方をテストし、どちらか早い方がペナルティを支払っているかどうかを確認してください。


不正行為のイラスト:

timer1.start();
double x1 = 2 * sqrt(n + 37 * y + exp(z));
timer1.stop();
timer2.start();
double x2 = 31 * sqrt(n + 37 * y + exp(z));
timer2.stop();

timer2期間が期間よりも短い場合timer1、31を掛ける方が2を掛けるよりも速いとは結論付けません。代わりに、コンパイラが共通部分式分析を実行し、コードが次のようになったことがわかります。

timer1.start();
double common = sqrt(n + 37 * y + exp(z));
double x1 = 2 * common;
timer1.stop();
timer2.start();
double x2 = 31 * common;
timer2.stop();

そして、証明された唯一のことは、31を掛けることは計算よりも速いということですcommon。これはまったく驚くべきことではありません-乗算はとよりもはるかに高速sqrtですexp

于 2012-05-31T17:58:55.313 に答える
19

編集:私のコードのOPのアセンブリリストを見ました。これがVS2011の一般的なバグでさえあるとは思えません。これは、OPのコードの特殊なケースのバグである可能性があります。OPのコードをclang3.2、gcc 4.6.2、およびVS2010でそのまま実行しましたが、すべての場合で最大差は約1%でした。

ne.cファイル/O2/GLフラグに適切な変更を加えてソースをコンパイルしただけです。これがソースです

int ne1(int n) {
 return n != 0;
 }

 int ne2(int n) {
 return n < 0 || n > 0;
 }

 int ne3(int n) {
 return !(n == 0);
 }

int main() { int p = ne1(rand()), q = ne2(rand()), r = ne3(rand());}

および対応するアセンブリ:

    ; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 

    TITLE   D:\llvm_workspace\tests\ne.c
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB OLDNAMES

EXTRN   @__security_check_cookie@4:PROC
EXTRN   _rand:PROC
PUBLIC  _ne3
; Function compile flags: /Ogtpy
;   COMDAT _ne3
_TEXT   SEGMENT
_n$ = 8                         ; size = 4
_ne3    PROC                        ; COMDAT
; File d:\llvm_workspace\tests\ne.c
; Line 11
    xor eax, eax
    cmp DWORD PTR _n$[esp-4], eax
    setne   al
; Line 12
    ret 0
_ne3    ENDP
_TEXT   ENDS
PUBLIC  _ne2
; Function compile flags: /Ogtpy
;   COMDAT _ne2
_TEXT   SEGMENT
_n$ = 8                         ; size = 4
_ne2    PROC                        ; COMDAT
; Line 7
    xor eax, eax
    cmp eax, DWORD PTR _n$[esp-4]
    sbb eax, eax
    neg eax
; Line 8
    ret 0
_ne2    ENDP
_TEXT   ENDS
PUBLIC  _ne1
; Function compile flags: /Ogtpy
;   COMDAT _ne1
_TEXT   SEGMENT
_n$ = 8                         ; size = 4
_ne1    PROC                        ; COMDAT
; Line 3
    xor eax, eax
    cmp DWORD PTR _n$[esp-4], eax
    setne   al
; Line 4
    ret 0
_ne1    ENDP
_TEXT   ENDS
PUBLIC  _main
; Function compile flags: /Ogtpy
;   COMDAT _main
_TEXT   SEGMENT
_main   PROC                        ; COMDAT
; Line 14
    call    _rand
    call    _rand
    call    _rand
    xor eax, eax
    ret 0
_main   ENDP
_TEXT   ENDS
END

ne2()を使用した<>および||演算子は明らかにより高価です。ne1()と演算子をそれぞれne3()使用するものは==、簡潔で同等です。!=

VisualStudio2011はベータ版です。これはバグだと思います。他の2つのコンパイラ、つまりgcc4.6.2clang3.2を使用したテストでは、O2最適化スイッチを使用して、Windows 7ボックスでの3つのテスト(私が行ったもの)すべてに対してまったく同じアセンブリが生成されました。要約は次のとおりです。

$ cat ne.c

#include <stdbool.h>
bool ne1(int n) {
    return n != 0;
}

bool ne2(int n) {
    return n < 0 || n > 0;
}

bool ne3(int n) {
    return !(n != 0);
}

int main() {}

gccでの収量:

_ne1:
LFB0:
    .cfi_startproc
    movl    4(%esp), %eax
    testl   %eax, %eax
    setne   %al
    ret
    .cfi_endproc
LFE0:
    .p2align 2,,3
    .globl  _ne2
    .def    _ne2;   .scl    2;  .type   32; .endef
_ne2:
LFB1:
    .cfi_startproc
    movl    4(%esp), %edx
    testl   %edx, %edx
    setne   %al
    ret
    .cfi_endproc
LFE1:
    .p2align 2,,3
    .globl  _ne3
    .def    _ne3;   .scl    2;  .type   32; .endef
_ne3:
LFB2:
    .cfi_startproc
    movl    4(%esp), %ecx
    testl   %ecx, %ecx
    sete    %al
    ret
    .cfi_endproc
LFE2:
    .def    ___main;    .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 2,,3
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB3:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    call    ___main
    xorl    %eax, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE3:

とclangで:

    .def     _ne1;
    .scl    2;
    .type   32;
    .endef
    .text
    .globl  _ne1
    .align  16, 0x90
_ne1:
    cmpl    $0, 4(%esp)
    setne   %al
    movzbl  %al, %eax
    ret

    .def     _ne2;
    .scl    2;
    .type   32;
    .endef
    .globl  _ne2
    .align  16, 0x90
_ne2:
    cmpl    $0, 4(%esp)
    setne   %al
    movzbl  %al, %eax
    ret

    .def     _ne3;
    .scl    2;
    .type   32;
    .endef
    .globl  _ne3
    .align  16, 0x90
_ne3:
    cmpl    $0, 4(%esp)
    sete    %al
    movzbl  %al, %eax
    ret

    .def     _main;
    .scl    2;
    .type   32;
    .endef
    .globl  _main
    .align  16, 0x90
_main:
    pushl   %ebp
    movl    %esp, %ebp
    calll   ___main
    xorl    %eax, %eax
    popl    %ebp
    ret

私の提案は、これをMicrosoftConnectのバグとして提出することです。

注:対応するC ++コンパイラーを使用してもここで大きな変更が生じるとは思わないため、Cソースとしてコンパイルしました。

于 2012-05-31T18:10:10.893 に答える