为啥 strcmp 比我的函数快这么多?
Posted
技术标签:
【中文标题】为啥 strcmp 比我的函数快这么多?【英文标题】:Why is strcmp so much faster than my function?为什么 strcmp 比我的函数快这么多? 【发布时间】:2014-01-11 03:56:11 【问题描述】:我写了一个函数Str::Compare
,它基本上是用另一种方式重写的strcmp
。
在比较这两个函数时,在一个重复 500'000'000 次的循环中,strcmp
执行速度太快了,大约快 x750 倍。
此代码在 C 库中编译,-Os
参数处于活动状态:
int Str::Compare(char* String_1, char* String_2)
char TempChar_1, TempChar_2;
do
TempChar_1 = *String_1++;
TempChar_2 = *String_2++;
while(TempChar_1 && TempChar_1 == TempChar_2);
return TempChar_1 - TempChar_2;
那个函数的执行时间是3.058s
,而strcmp
只有0.004s
。
为什么会这样?
这也是我实现基准循环的方式:
int main()
char Xx[] = "huehuehuehuehuehuehuehuehuehuehuehuehuehue",
Yy[] = "huehuehuehuehuehuehuehuehuehuehuehuehuehue";
for(int i = 0; i < 500000000; ++i)
Str::Compare(Xx, Yy);
编辑:
在测试我编写的一些代码和优化时,Str::Compare
的速度得到了极大的提高。
如果之前strcmp
的速度是 x750 倍,那么现在只有 x250。这是新代码:
int Str::Compare(char* String_1, char* String_2)
char TempChar_1, TempChar_2, TempChar_3;
while(TempChar_1 && !TempChar_3)
TempChar_1 = *String_1++;
TempChar_2 = *String_2++;
TempChar_3 = TempChar_1 ^ TempChar_2;
return TempChar_1 - TempChar_2;
新的执行时间是0.994s
。
【问题讨论】:
你为什么不做while(*str1++ == *str2++);
因为那段代码是这个函数最低效的实现。
1
Str::Compare
的编辑版本不好-TempChar_3
在初始化之前使用,2
VS2010为原始功能生成的代码几乎和strcmp
一样快。 3
很难通过简单的实现来击败大规模优化的功能(甚至是内在的!)。
解释为什么?该代码类似于 strcpy afaik 的标准 imp
while(*str_1 && *str_1++ == *str_2++);
其实是正确的形式。这更慢,原因很简单,处理器必须每次都解析指针的地址,并且性能损失。
【参考方案1】:
我相信大多数标准库都是用汇编语言编写的。这可能是您看到标准库比您的更快的原因。
【讨论】:
这可能是真的,但它不会解释 750 倍的速度差异。这比解释语言和编译语言之间的区别更大。通常,当我看到如此大的差异时,我会怀疑缓存未命中甚至分支预测失败之类的事情。【参考方案2】:在比较性能时,我发现最好将测试函数和测试驱动程序放在单独的编译单元中。将您的测试函数放在单独的编译单元中,并将它们编译到您想要的任何优化级别,但编译未优化的测试驱动程序。否则你会遇到你在这里看到的那种问题。
问题在于strcmp
比较两个const
C 风格的字符串。如果您在 strcmp(string_a, string_b)
上循环 500,000,000 次,优化编译器将足够聪明地减少该循环以优化该循环,然后可能足够聪明以优化对 strcmp
的剩余调用。
您的比较函数需要两个非常量字符串。就编译器而言,您的函数很可能有副作用。编译器不知道,因此它无法将循环优化到零。它必须生成代码来执行 500,000,000 次比较。
【讨论】:
const
指针参数的限定通常不会影响编译器推断副作用的能力。只要原始数组对象是非常量的,就可以抛弃常量。此外,该函数可能会调用其他导致字符串修改或别名的东西。【参考方案3】:
我对此感到好奇并构建了一个测试程序:
#include <string.h>
compare(char* String_1, char* String_2)
char TempChar_1,
TempChar_2;
do
TempChar_1 = *String_1++;
TempChar_2 = *String_2++;
while(TempChar_1 && TempChar_1 == TempChar_2);
return TempChar_1 - TempChar_2;
int main()
int i=strcmp("foo","bar");
int j=compare("foo","bar");
return i;
我使用 gcc 4.7.3 将其编译为带有 gcc -S -Os test.c
的汇编器,得到以下汇编器:
.file "test.c"
.text
.globl compare
.type compare, @function
compare:
.LFB24:
.cfi_startproc
xorl %edx, %edx
.L2:
movsbl (%rdi,%rdx), %eax
movsbl (%rsi,%rdx), %ecx
incq %rdx
cmpb %cl, %al
jne .L4
testb %al, %al
jne .L2
.L4:
subl %ecx, %eax
ret
.cfi_endproc
.LFE24:
.size compare, .-compare
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "bar"
.LC1:
.string "foo"
.section .text.startup,"ax",@progbits
.globl main
.type main, @function
main:
.LFB25:
.cfi_startproc
movl $.LC0, %esi
movl $.LC1, %edi
call compare
movl $1, %eax
ret
.cfi_endproc
.LFE25:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3"
.section .note.GNU-stack,"",@progbits
我在 x86 汇编器中不是那么好,但据我所知,对 strcmp 的调用已被删除,并简单地替换为常量表达式 (movl $1, %eax
)。因此,如果您在测试中使用常量表达式,gcc 可能会将 strcmp 优化为常量。
【讨论】:
Gcc 也可以用常量替换对 strlen 的调用 Here's G++ 4.8.1 reducing everything to a constant at Coliru.strcmp
即使在我没有使用常量的我的程序中也被替换为常量表达式。我会努力更好地理解为什么会这样。以上是关于为啥 strcmp 比我的函数快这么多?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的 foreach 比我的 for 循环快? [复制]