Ich habe festgestellt, dass! = Und == nicht die schnellsten Methoden 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 zweiten Versionen beider Vergleiche waren sowohl auf x86-32 als auch auf x86-64 ~ 12% schneller. Auf x86-64 waren die Anweisungen jedoch identisch (die ersten Versionen sahen genauso aus wie die zweiten Versionen), aber die zweiten Versionen waren immer noch schneller.
- Warum generiert der Compiler die schnellere Version auf x86-32 nicht?
- Warum sind die zweiten Versionen auf x86-64 noch schneller, wenn die Assembly-Ausgabe identisch ist?
EDIT: Ich habe Benchmarking-Code hinzugefügt. NULL: 1544 ms, 1358 ms NON_ZERO: 1544 ms, 1358 ms 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 einzelnen Quelldatei kompiliert werden, da main.asm ziemlich groß ist. Ich hatte null1, null2, nonZero1, nonZero2 in einer separaten Quelldatei.
EDIT2: Könnte jemand, auf dem sowohl VC ++ 11 als auch VC ++ 2010 installiert sind, den Benchmarking-Code ausführen und die Timings veröffentlichen? Es könnte tatsächlich ein Fehler in VC ++ 11 sein.
quelle
xor
Anweisungen? Sie sehen für den Test nicht relevant aus, daher sollte er Teil des umgebenden Codes sein.xor
: ed hat und dass das für den nächsten gültig bleibt ...eax
Antworten:
Kompilieren Sie einfach die Quellen mit geeigneten Änderungen an meiner
ne.c
Datei und den/O2
und/GL
-Flaggen. Hier ist die Quelleint 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 Baugruppe:
; 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()
die verwendet<
,>
und||
Betreiber ist deutlich teurer.ne1()
undne3()
die die Operatoren==
und!=
verwenden, sind terser und äquivalent.Visual Studio 2011 befindet sich in der Beta . Ich würde dies als Fehler betrachten. Meine Tests mit zwei anderen Compilern, nämlich gcc 4.6.2 und clang 3.2 , mit dem
O2
Optimierungsschalter ergaben für alle drei Tests (die ich hatte) auf meiner Windows 7-Box genau die gleiche Baugruppe. 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() {}
ergibt 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 klirren:
.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 Fehler bei Microsoft Connect einzureichen .
Hinweis: Ich habe sie als C-Quelle kompiliert, da ich nicht glaube, dass die Verwendung des entsprechenden C ++ - Compilers hier wesentliche Änderungen bewirken würde.
quelle
n = 10
immer ermittelt hat . Darüber hinaus wurden die Funktionsaufrufe vollständig eliminiert, da das Ergebnis nicht verwendet wurde und keine Nebenwirkungen auftreten.Dies ist eine großartige Frage, aber ich denke, Sie sind der Abhängigkeitsanalyse des Compilers zum Opfer gefallen.
Der Compiler muss die hohen Bits nur
eax
einmal löschen, und sie bleiben für die zweite Version gelöscht. Die zweite Version müsste den Preis dafür zahlen,xor eax, eax
außer dass die Compiler-Analyse bewiesen hat, dass sie von der ersten Version gelöscht wurde.Die zweite Version kann "schummeln", indem sie die Arbeit des Compilers in der ersten Version nutzt.
Wie messen Sie die 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 durch (kompilieren Sie stattdessen für jede Version neu). Wenn Sie dies tun, testen Sie sowohl "Version A zuerst" als auch "Version B zuerst" und prüfen Sie, ob derjenige, der zuerst kommt, 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();
Wenn die
timer2
Dauer kleiner als dietimer1
Dauer ist, schließen wir nicht, dass das Multiplizieren mit 31 schneller ist als das Multiplizieren mit 2. Stattdessen stellen wir fest, dass der Compiler eine allgemeine Analyse des Unterausdrucks durchgeführt hat und der Code wie folgt lautet: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 das einzige, was bewiesen ist, ist, dass das Multiplizieren mit 31 schneller ist als das Rechnen
common
. Was überhaupt nicht überrascht - die Multiplikation ist weitaus schneller alssqrt
undexp
.quelle
(common << 5) - common
?