问题描述
|
我有此代码(我的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秒!
为什么会有如此大的差异?
解决方法
可能是因为后递增运算符(在
while
语句的条件下使用)涉及保留具有旧值的变量的临时副本。
“ѭ1”的真正含义是:
while (tmp = *str,++str,tmp)
...
相比之下,当您在while循环的主体中将“ 8”作为单个语句编写时,它处于空上下文中,因此不会获取旧值,因为不需要它。
总结一下,在*str++
情况下,您有一个分配,2个增量和每个循环迭代中的跳转。在其他情况下,您只有2个增量和一个跳跃。
, 在ideone.com上尝试一下,我在这里用* str ++得到了0.5s的执行时间。没有它,只需要一秒钟(这里)。使用* str ++更快。也许对* str ++的优化可以更有效地完成。
, 这取决于您的编译器,编译器标志和体系结构。使用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++
版本可以完全相同地编译(由于对ѭ13outside的更改在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
,其他人已经提供了一些出色的注释,包括对生成的汇编代码的分析。我强烈建议您仔细阅读它们。正如他们指出的那样,如果不进行量化,就无法真正回答这类问题,所以让我们来解决一下。
首先,我们需要一个程序。我们的计划是这样的:我们将生成长度为2的幂的字符串,然后依次尝试所有函数。我们先运行一次以准备缓存,然后使用可用的最高分辨率分别对4096次迭代进行时间。完成后,我们将计算一些基本统计数据:最小值,最大值和简单移动平均值,并将其转储。然后,我们可以进行一些基本分析。
除了您已经展示的两种算法之外,我还将展示第三个选项,它根本不涉及使用计数器,而是依靠减法,并且我将抛出的结果混合起来std::strlen
,只是看看会发生什么。这将是一个有趣的失败。
通过电视的魔力,我们的小程序已经编写好了,因此我们用gcc -std=c++11 -O3 speed.c
编译它,然后开始生成一些数据。我已经完成了两个单独的图,一个用于大小从32到8192字节的字符串,另一个用于大小从16384一直到1048576字节的字符串。在以下图表中,Y轴是消耗的时间(以纳秒为单位),X轴显示字符串的长度(以字节为单位)。
事不宜迟,让我们来看一下32到8192字节的“小”字符串的性能:
现在,这很有趣。 std::strlen
功能不仅在整体上胜过所有功能,而且由于其性能更加稳定,因此也可以做到无忧无虑。
如果我们查看更大的字符串(从16384一直到1048576字节长),情况会改变吗?
有点。差异变得更加明显。随着我们自定义编写的函数不断旋转,std::strlen
继续表现出色。
一个有趣的发现是,您不必将许多C ++指令(甚至是汇编指令)转换为性能,因为其主体由较少指令组成的函数有时需要更长的执行时间。
一个更加有趣且重要的观察结果是注意到str::strlen
函数的性能如何。
那么,这一切使我们得到什么呢?
第一个结论:不要重新发明轮子。使用可用的标准功能。它们不仅已经编写,而且已经非常非常优化,并且几乎可以肯定会胜过您可以写的任何东西,除非您是Agner Fog。
第二个结论:除非您从探查器获得的硬数据表明应用程序中的特定部分是代码或函数的热点,否则不要费心优化代码。众所周知,程序员无法通过查看高级功能来检测热点。
第三个结论:更喜欢算法优化,以提高代码的性能。全神贯注地工作,让编译器将位乱七八糟。
您最初的问题是:“为什么函数slen2
比ѭ22慢?”我可以说,没有太多的信息就很难回答,即使那样,它也可能比您关心的更长,更复杂。对于。相反,我要说的是:
谁在乎为什么?你为什么还要为此烦恼呢?使用std::strlen
-比您可以装配的任何东西都要好-并继续解决更重要的问题-因为我敢肯定这不是您应用程序中最大的问题。