我有这个代码(我的strlen函数)
size_t slen(const char *str)
{
size_t len = 0;
while (*str)
{
len++;
str++;
}
return len;
}
执行while (*str++)
,如下所示,程序执行时间要大得多:
while (*str++)
{
len++;
}
我这样做是为了探测代码
int main()
{
double i = 11002110;
const char str[] = "long string here blablablablablablablabla"
while (i--)
slen(str);
return 0;
}
在第一种情况下,执行时间约为6.7秒,而在第二种情况下(使用*str++
),时间约为10秒!
为什么会有这么大的差异?
答案 0 :(得分:6)
可能是因为后增量运算符(在while
语句的条件中使用)涉及使用旧值保留变量的临时副本。
while (*str++)
的真正含义是:
while (tmp = *str, ++str, tmp)
...
相比之下,当您在while循环体中将str++;
作为单个语句编写时,它处于void上下文中,因此不会获取旧值,因为它不需要。
总而言之,在*str++
情况下,您有一个赋值,2个增量,并且在循环的每次迭代中都有一个跳转。在另一种情况下,你只有2个增量和一个跳跃。
答案 1 :(得分:2)
答案 2 :(得分:1)
这取决于您的编译器,编译器标志和您的体系结构。使用Apple的LLVM gcc 4.2.1,我在两个版本之间的性能没有明显变化,而且确实不应该。一个好的编译器会将*str
版本变成类似
IA-32(AT& T语法):
slen:
pushl %ebp # Save old frame pointer
movl %esp, %ebp # Initialize new frame pointer
movl -4(%ebp), %ecx # Load str into %ecx
xor %eax, %eax # Zero out %eax to hold len
loop:
cmpb (%ecx), $0 # Compare *str to 0
je done # If *str is NUL, finish
incl %eax # len++
incl %ecx # str++
j loop # Goto next iteration
done:
popl %ebp # Restore old frame pointer
ret # Return
*str++
版本可以编译完全相同(因为str
的更改在slen
之外不可见,实际发生的增量并不重要),或者正文循环可以是:
loop:
incl %ecx # str++
cmpb -1(%ecx), $0 # Compare *str to 0
je done # If *str is NUL, finish
incl %eax # len++
j loop # Goto next iteration
答案 3 :(得分:1)
其他人已经提供了一些优秀的评论,包括对生成的汇编代码的分析。我强烈建议您仔细阅读。正如他们已经指出的那样,在没有一些量化的情况下,这个问题真的无法得到解答,所以让我们稍微玩一下。
首先,我们需要一个程序。我们的计划是:我们将生成长度为2的幂的字符串,并依次尝试所有函数。我们运行一次以填充缓存,然后使用我们可用的最高分辨率分别计时4096次迭代。完成后,我们将计算一些基本统计数据:min,max和简单移动平均值并将其转储。然后我们可以进行一些基本的分析。
除了您已经展示过的两种算法之外,我还会展示第三种选项,它根本不涉及使用计数器,而是依赖于减法,我会说通过投掷std::strlen
来混淆,只是为了看看会发生什么。这将是一个有趣的失败。
通过电视的魔力,我们的小程序已经编写好了,所以我们用gcc -std=c++11 -O3 speed.c
编译它,我们开始产生一些数据。我做了两个单独的图,一个用于大小为32到8192字节的字符串,另一个用于大小从16384一直到1048576字节长的字符串。在下图中,Y轴是以纳秒为单位消耗的时间,X轴以字节为单位显示字符串的长度。
不用多说,让我们来看看" small"字符串从32到8192字节:
现在这个很有意思。 std::strlen
功能不仅能够全面胜过所有内容,而且还非常有趣,因为它的性能更加稳定。
如果我们查看更大的字符串,从16384一直到1048576字节,情况是否会改变?
排序。差异变得更加明显。由于我们的自定义编写功能让人感到兴奋,std::strlen
继续表现令人钦佩。
有趣的观察结果是,您无法将C ++指令的数量(甚至是汇编指令的数量)转换为性能,因为其主体由较少指令组成的函数有时需要更长时间才能执行。
更有趣的 - 并且重要观察是注意str::strlen
函数的执行情况。
第一个结论:不要重新发明轮子。使用您可以使用的标准功能。它们不仅已经编写好,而且非常优化,几乎肯定会超越你能写的任何东西,除非你Agner Fog。
第二个结论:除非您从分析器中获得硬数据,否则代码或函数的特定部分在您的应用程序中是热点,不要打扰优化代码。众所周知,程序员通过查看高级功能来检测热点是非常糟糕的。
第三个结论:更喜欢算法优化,以提高代码的性能。把你的思想付诸实践,让编译器改变现状。
您的原始问题是:"为什么功能slen2
慢于slen1
?"我可以说,如果没有更多的信息,它就不容易回答,即使这样,它也可能比你所关心的更长,更复杂。相反,我所说的是:
谁在乎为什么?你为什么还要烦恼呢?使用std::strlen
- 这比你可以装备的任何东西都要好 - 然后继续解决更重要的问题 - 因为我确定这个不是最大的你的申请中的问题。