sintaxis relacionales programas programacion operadores lógicos los logicos lenguaje ejemplos dev con asignacion aritmeticos c++ performance assembly machine-code

c++ - relacionales - operadores int!=y== cuando se compara con cero



operadores relacionales en c++ (2)

Descubrí que! = Y == no son las formas más rápidas para probar cero o no cero.

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

Compilador: VC ++ 11 Indicadores de optimización: / O2 / GL / LTCG

Esta es la salida de ensamblaje para x86-32. Las segundas versiones de ambas comparaciones fueron ~ 12% más rápidas tanto en x86-32 como en x86-64. Sin embargo, en x86-64 las instrucciones eran idénticas (las primeras versiones se veían exactamente como las segundas versiones), pero las segundas versiones eran aún más rápidas.

  1. ¿Por qué el compilador no genera la versión más rápida en x86-32?
  2. ¿Por qué las segundas versiones son aún más rápidas en x86-64 cuando la salida del ensamblaje es idéntica?

EDITAR: He agregado código de evaluación comparativa. ZERO: 1544ms, 1358ms NON_ZERO: 1544ms, 1358ms http://pastebin.com/m7ZSUrcP o http://anonymouse.org/cgi-bin/anon-www.cgi/http://pastebin.com/m7ZSUrcP

Nota: Probablemente no sea conveniente ubicar estas funciones cuando se compilan en un único archivo fuente, porque main.asm es bastante grande. Tenía zero1, zero2, nonZero1, nonZero2 en un archivo fuente separado.

EDIT2: ¿Podría alguien con VC ++ 11 y VC ++ 2010 instalado ejecutar el código de evaluación comparativa y publicar los tiempos? De hecho, podría ser un error en VC ++ 11.


EDITAR: vi la lista de montaje de OP para mi código. Dudo que esto sea un error general con VS2011 ahora. Esto puede ser simplemente un error de caso especial para el código de OP. Ejecuté el código de OP tal como está con clang 3.2, gcc 4.6.2 y VS2010 y en todos los casos las diferencias máximas fueron de ~ 1%.

Recién compilé las fuentes con las modificaciones adecuadas en mi archivo ne.c y las banderas /O2 y /GL . Aquí está la fuente

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

y la asamblea correspondiente:

; 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() que usó < , > y || operadores es claramente más caro. ne1() y ne3() que usan los operadores == y != respectivamente, son más estrictos y equivalentes.

Visual Studio 2011 está en beta . Yo consideraría esto como un error. Mis pruebas con otros dos compiladores, a saber, gcc 4.6.2 y clang 3.2 , con el conmutador de optimización O2 produjeron exactamente el mismo ensamblaje para las tres pruebas (que tuve) en mi caja de Windows 7. Aquí hay un resumen:

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

rendimientos con 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:

y con 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

Mi sugerencia sería presentar esto como un error con Microsoft Connect .

Nota: los compilé como fuente C, ya que no creo que usar el compilador C ++ correspondiente haga un cambio significativo aquí.


Esta es una gran pregunta, pero creo que has sido víctima del análisis de dependencia del compilador.

El compilador solo tiene que borrar los bits altos de eax una vez, y se mantienen claros para la segunda versión. La segunda versión tendría que pagar el precio a xor eax, eax excepto que el análisis del compilador demostró que la primera versión lo dejó libre.

La segunda versión es capaz de "hacer trampa" aprovechando el trabajo que el compilador hizo en la primera versión.

¿Cómo estás midiendo los tiempos? ¿Es "(versión uno, seguido de la versión dos) en un bucle" o "(versión uno en un bucle) seguido de (versión dos en un bucle)"?

No haga ambas pruebas en el mismo programa (en su lugar, vuelva a compilar para cada versión) o, si lo hace, pruebe primero la "versión A primero" y la "versión B primero" y vea si lo que ocurra primero es pagar una penalización.

Ilustración de la trampa:

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

Si la duración del timer2 es menor que la duración del timer1 , no concluimos que multiplicar por 31 es más rápido que multiplicar por 2. En su lugar, nos damos cuenta de que el compilador realizó un análisis de subexpresión común, y el código se convirtió en:

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

Y lo único probado es que multiplicar por 31 es más rápido que computar en common . Lo cual no es sorprendente en absoluto: la multiplicación es mucho más rápida que sqrt y exp .