int运算符!=和==比较时为零

我发现!=和==不是用于测试零或非零的最快方法。

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的汇编输出。 这两种比较的第二个版本在x86-32和x86-64上都快了12%。 但是,在x86-64上,指令是相同的(第一个版本看起来与第二个版本完全相同),但第二个版本仍然更快。

  • 为什么编译器不能在x86-32上生成更快的版本?
  • 为什么当组件输出相同时,x86-64上的第二个版本仍然更快?
  • 编辑:我添加了基准代码。 ZERO:1544ms,1358ms NON_ZERO:1544ms,1358ms http://pastebin.com/m7ZSUrcP或http://anonymouse.org/cgi-bin/anon-www.cgi/http://pastebin.com/m7ZSUrcP

    注意:在单个源文件中编译这些函数时可能不方便,因为main.asm相当大。 我有一个单独的源文件中的zero1,zero2,nonZero1,nonZero2。

    编辑2:可以同时安装VC ++ 11和VC ++ 2010的人运行基准测试代码并发布计时? 这可能确实是VC ++ 11中的一个错误。


    编辑:看到我的代码OP的程序集列表。 我怀疑这是VS2011的一个常见错误。 这可能只是OP代码的一个特例。 我运行OP的代码原来是3.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_workspacetestsne.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_workspacetestsne.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()使用了<>|| 运营商显然比较昂贵。 分别使用==!=运算符的ne3() ne1()ne3()是比较清晰和等价的。

    Visual Studio 2011处于测试阶段。 我会认为这是一个错误。 我的另外两个编译器gcc 4.6.2和clang 3.2的测试结果与O2优化开关在我的Windows 7盒子上产生了完全相同的三个测试(我有)。 以下是一个总结:

    $ 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:
    

    和叮当声:

        .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
    

    我的建议是将此文件作为Microsoft Connect的错误提交。

    注意:我将它们编译为C源代码,因为我不认为使用相应的C ++编译器会在这里做出任何重大更改。


    这是一个很好的问题,但我认为你已经成为编译器依赖分析的受害者。

    编译器只需要清除一次eax的高位,并且它们在第二个版本中保持清晰。 第二个版本将不得不支付价格给xor eax, eax除了编译器分析证明它已被第一个版本清除。

    第二个版本可以通过利用编译器在第一个版本中所做的工作来“欺骗”。

    你如何衡量时间? 它是“(版本一,其次是版本二)在一个循环中”,或者“(循环中的版本一),然后是(循环中的版本二)”?

    不要在同一个程序中进行两次测试(而是针对每个版本进行重新编译),或者如果是这样,则首先测试“版本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要快。 这并不令人惊讶 - 乘积比sqrtexp要快得多。

    链接地址: http://www.djcxy.com/p/31505.html

    上一篇: int operators != and == when comparing to zero

    下一篇: How do I achieve the theoretical maximum of 4 FLOPs per cycle?