为啥比较 char 变量两次比比较短变量一次快

Posted

技术标签:

【中文标题】为啥比较 char 变量两次比比较短变量一次快【英文标题】:why comparing char variables twice is faster than comparing short variables once为什么比较 char 变量两次比比较短变量一次快 【发布时间】:2013-12-25 09:48:06 【问题描述】:

我认为一个比较必须比两个快。但是经过我的测试,我发现在调试模式下短比较快一点,而在发布模式下,字符比较更快。我想知道真正的原因。

以下是测试代码和测试结果。我写了两个简单的函数,func1() 使用两个字符比较,func2() 使用一个短比较。主函数返回临时返回值以避免编译优化忽略我的测试代码。我的编译器是 GCC 4.7.2,CPU Intel® Xeon® CPU E5-2430 0 @ 2.20GHz (VM)。

inline int func1(unsigned char word[2])

        if (word[0] == 0xff && word[1] == 0xff)
                return 1;
        return 0;


inline int func2(unsigned char word[2])

        if (*(unsigned short*)word == 0xffff)
                return 1;
        return 0;


int main()

        int n_ret = 0;
        for (int j = 0; j < 10000; ++j)
                for (int i = 0; i < 70000; ++i)
                        n_ret += func2((unsigned char*)&i);
        return n_ret;

调试模式:

          func1      func2
real    0m3.621s    0m3.586s
user    0m3.614s    0m3.579s
sys     0m0.001s    0m0.000s

发布模式:

          func1      func2
real    0m0.833s    0m0.880s
user    0m0.831s    0m0.878s
sys     0m0.000s    0m0.002s

func1版的汇编代码:

        .cfi_startproc
        movl    $10000, %esi
        xorl    %eax, %eax
        .p2align 4,,10
        .p2align 3
.L6:
        movl    $1, %edx
        xorl    %ecx, %ecx
        .p2align 4,,10
        .p2align 3
.L8:
        movl    %edx, -24(%rsp)
        addl    $1, %edx
        addl    %ecx, %eax
        cmpl    $70001, %edx
        je      .L3
        xorl    %ecx, %ecx
        cmpb    $-1, -24(%rsp)
        jne     .L8
        xorl    %ecx, %ecx
        cmpb    $-1, -23(%rsp)
        sete    %cl
        jmp     .L8
        .p2align 4,,10
        .p2align 3
.L3:
        subl    $1, %esi
        jne     .L6
        rep
        ret
        .cfi_endproc

func2版的汇编代码:

        .cfi_startproc
        movl    $10000, %esi
        xorl    %eax, %eax
        .p2align 4,,10
        .p2align 3
.L4:
        movl    $1, %edx
        xorl    %ecx, %ecx
        jmp     .L3
        .p2align 4,,10
        .p2align 3
.L7:
        movzwl  -24(%rsp), %ecx
.L3:
        cmpw    $-1, %cx
        movl    %edx, -24(%rsp)
        sete    %cl
        addl    $1, %edx
        movzbl  %cl, %ecx
        addl    %ecx, %eax
        cmpl    $70001, %edx
        jne     .L7
        subl    $1, %esi
        jne     .L4
        rep
        ret
        .cfi_endproc

【问题讨论】:

1) 是否一直更快?或者这只是一个小故障? 2) 向我们展示生成的汇编代码。 嗯...那些时间远远超出了测量的不确定性...此外,调试构建(==没有优化)没有有意义的时间/复杂性配置文件。此外,第二个函数调用了未定义的行为,因此甚至不值得推理。 您必须展示编译器在每种情况下生成的汇编代码,才能使对该主题的任何讨论有意义。 C 代码没有内在性能。 (从技术上讲,汇编代码也不能没有目标处理器,但我们假设一个现代的广泛可用的处理器) 这些数字几乎相同。一次关闭会受到操作系统切换等的影响。至少运行几百次。 @PascalCuoq 事实并非如此,因为&amp;iint * 【参考方案1】:

在 GCC 4.6.3 中,第一段和第二段代码的代码不同,如果运行足够长的时间,func1 选项的运行时间会明显变慢。不幸的是,由于您的运行时间很短,两者在时间上看起来相似。

将外循环增加 10 倍意味着 func2 大约需要 6 秒,而 func1 大约需要 10 秒。这是使用gcc -std=c99 -O3 编译代码。

我预计,主要区别在于 && 语句引入的额外分支。额外的xorl %ecx, %ecx 并没有多大帮助(我得到了相同的结果,尽管我的代码在标签名称方面看起来略有不同)。

编辑:我确实尝试过使用 and 而不是分支来提出无分支解决方案,但编译拒绝内联函数,因此需要 30 秒而不是 10 秒。

基准运行于:

AMD Phenom(tm) II X4 965

以 3.4 GHz 运行。

【讨论】:

我测试了 10x 外循环,两个函数的成本时间都增加了 10 倍,分别为 8.35s 和 8.79s。 显然,你的处理器运行方式与我的不同——不管是在 6 秒内运行 func2 版本的处理器还是在 8 秒内运行 func1 的处理器更好...... 既然我们一直在走这条路,你们能用你们的 CPU 类型更新你的帖子吗? 我不知道and 是什么,但是作为迈向无分支的第一步,用&amp; 代替&amp;&amp; 怎么样? 我怀疑分支预测器是这里的一个因素。如果我将第一个函数更改为进行单字节比较(注释掉 &amp;&amp; word[1] == 0xFF),我的 Phenom II 上的两个函数之间的时序相同(实际上与 Mats 的处理器相同)。

以上是关于为啥比较 char 变量两次比比较短变量一次快的主要内容,如果未能解决你的问题,请参考以下文章

unsigned 变量为啥不能和0进行比较

Java数据结构Day14--冒泡排序优化

为啥不能比较两个 int 类型变量的值? [复制]

为啥将变量与 0.0 进行比较会返回 false? [复制]

为啥 == 在比较具有相同 int 值的两个对象类型变量时不起作用

c语言中,为啥不能对字符指针变量指向的字符串再赋值?