为啥比较 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 事实并非如此,因为&i
是int *
。
【参考方案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
是什么,但是作为迈向无分支的第一步,用&
代替&&
怎么样?
我怀疑分支预测器是这里的一个因素。如果我将第一个函数更改为进行单字节比较(注释掉 && word[1] == 0xFF
),我的 Phenom II 上的两个函数之间的时序相同(实际上与 Mats 的处理器相同)。以上是关于为啥比较 char 变量两次比比较短变量一次快的主要内容,如果未能解决你的问题,请参考以下文章
为啥将变量与 0.0 进行比较会返回 false? [复制]