Frage int Operatoren! = und == beim Vergleich mit Null


Ich habe festgestellt, dass! = Und == nicht die schnellsten Möglichkeiten zum Testen auf Null oder Nicht-Null sind.

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

Compiler: VC ++ 11 Optimierungsflags: / O2 / GL / LTCG

Dies ist die Assembly-Ausgabe für x86-32. Die zweite Version beider Vergleiche war ~ 12% schneller sowohl bei x86-32 als auch bei x86-64. Auf x86-64 waren die Anweisungen jedoch identisch (die ersten Versionen sahen genauso aus wie die zweiten Versionen), aber die zweiten Versionen waren noch schneller.

  1. Warum generiert der Compiler nicht die schnellere Version auf x86-32?
  2. Warum sind die zweiten Versionen auf x86-64 noch schneller, wenn die Assembly-Ausgabe identisch ist?

EDIT: Ich habe Benchmark-Code hinzugefügt. ZERO: 1544ms, 1358ms NON_ZERO: 1544ms, 1358ms http://pastebin.com/m7ZSUrcP oder http://anonymouse.org/cgi-bin/anon-www.cgi/http://pastebin.com/m7ZSUrcP

Hinweis: Es ist wahrscheinlich unpraktisch, diese Funktionen zu finden, wenn sie in einer einzigen Quelldatei kompiliert werden, da main.asm ziemlich groß ist. Ich hatte Zero1, Zero2, NonZero1, NonZero2 in einer separaten Quelldatei.

EDIT2: Könnte jemand, der sowohl VC ++ 11 als auch VC ++ 2010 installiert hat, den Benchmarking-Code ausführen und die Timings posten? Es könnte tatsächlich ein Fehler in VC ++ 11 sein.


75
2018-05-31 17:50


Ursprung


Antworten:


BEARBEITEN: OP-Assembly-Liste für meinen Code angezeigt. Ich bezweifle, dass das überhaupt ein ist allgemeiner Fehler mit VS2011 jetzt. Dies kann einfach ein Spezialfall für den OP-Code sein. Ich habe den OP-Code wie gehabt mit clang 3.2, gcc 4.6.2 und VS2010 ausgeführt und in allen Fällen den maximale Unterschiede waren bei ~ 1%.

Ich habe die Quellen mit geeigneten Änderungen an meiner Seite zusammengestellt ne.c Datei und die /O2 und /GL Flaggen. Hier ist die Quelle

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());}

und die entsprechende Versammlung:

    ; 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() welches das benutzt <, > und || Betreiber ist deutlich teurer. ne1() und ne3() welche benutzen die == und != Betreiber sind jeweils terser und gleichwertig.

Visual Studio 2011 ist in der Betaversion. Ich würde dies als einen Fehler betrachten. Meine Tests mit zwei anderen Compilern nämlich gcc 4.6.2 und Klang 3.2, mit dem O2 Optimierungsschalter ergab die exakt gleiche Baugruppe für alle drei Tests (die ich hatte) auf meiner Windows 7-Box. Hier ist eine Zusammenfassung:

$ 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() {}

Erträge mit 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:

und mit Klängen:

    .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

Mein Vorschlag wäre, dies als Bug mit zu speichern Microsoft Connect.

Hinweis: Ich habe sie als C-Quelle kompiliert, da ich nicht denke, dass die Verwendung des entsprechenden C ++ - Compilers hier eine wesentliche Änderung vornehmen würde.


19
2018-05-31 18:10



Das ist eine gute Frage, aber ich denke, Sie sind der Abhängigkeitsanalyse des Compilers zum Opfer gefallen.

Der Compiler muss nur die hohen Bits von eax einmal, und sie bleiben für die zweite Version klar. Die zweite Version müsste den Preis bezahlen xor eax, eax außer dass die Compileranalyse bewiesen hat, dass sie von der ersten Version gelöscht wurde.

Die zweite Version kann "schummeln", indem sie die Arbeit nutzt, die der Compiler in der ersten Version geleistet hat.

Wie messen Sie Zeiten? Ist es "(Version eins, gefolgt von Version zwei) in einer Schleife" oder "(Version eins in einer Schleife) gefolgt von (Version zwei in einer Schleife)"?

Führen Sie nicht beide Tests im selben Programm aus (stattdessen für jede Version neu kompilieren), oder wenn Sie dies tun, testen Sie sowohl "Version A zuerst" als auch "Version B zuerst" und sehen Sie, ob der zuerst geleistete Fall eine Strafe zahlt.


Illustration des Betrugs:

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();

Ob timer2 Dauer ist kleiner als timer1 Dauer, schließen wir nicht, dass Multiplikation mit 31 ist schneller als mit 2 multiplizieren. Stattdessen erkennen wir, dass der Compiler gemeinsame Teilausdruck Analyse durchgeführt, und der Code wurde:

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

Und die einzige bewiesene Sache ist, dass die Multiplikation mit 31 schneller ist als die Berechnung common. Was überhaupt nicht verwunderlich ist - die Multiplikation ist viel schneller als sqrt und exp.


121
2018-05-31 17:58