* str和* str ++

时间:2011-05-30 19:58:55

标签: c pointers char

我有这个代码(我的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秒!

为什么会有这么大的差异?

4 个答案:

答案 0 :(得分:6)

可能是因为后增量运算符(在while语句的条件中使用)涉及使用旧值保留变量的临时副本。

while (*str++)的真正含义是:

while (tmp = *str, ++str, tmp)
  ...

相比之下,当您在while循环体中将str++;作为单个语句编写时,它处于void上下文中,因此不会获取旧值,因为它不需要。

总而言之,在*str++情况下,您有一个赋值,2个增量,并且在循环的每次迭代中都有一个跳转。在另一种情况下,你只有2个增量和一个跳跃。

答案 1 :(得分:2)

在ideone.com上试试这个,我用* str ++ here执行约0.5秒。没有,只需要一秒钟(here)。使用* str ++更快。也许通过优化* str ++可以更有效地完成。

答案 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字节:

Performance Plot - Small Strings

现在这个很有意思。 std::strlen功能不仅能够全面胜过所有内容,而且还非常有趣,因为它的性能更加稳定。

如果我们查看更大的字符串,从16384一直到1048576字节,情况是否会改变?

enter image description here

排序。差异变得更加明显。由于我们的自定义编写功能让人感到兴奋,std::strlen继续表现令人钦佩。

有趣的观察结果是,您无法将C ++指令的数量(甚至是汇编指令的数量)转换为性能,因为其主体由较少指令组成的函数有时需要更长时间才能执行。

更有趣的 - 并且重要观察是注意str::strlen函数的执行情况。

那么这一切对我们有什么影响?

第一个结论:不要重新发明轮子。使用您可以使用的标准功能。它们不仅已经编写好,而且非常优化,几乎肯定会超越你能写的任何东西,除非你Agner Fog

第二个结论:除非您从分析器中获得硬数据,否则代码或函数的特定部分在您的应用程序中是热点,不要打扰优化代码。众所周知,程序员通过查看高级功能来检测热点是非常糟糕的。

第三个结论:更喜欢算法优化,以提高代码的性能。把你的思想付诸实践,让编译器改变现状。

您的原始问题是:"为什么功能slen2慢于slen1?"我可以说,如果没有更多的信息,它就不容易回答,即使这样,它也可能比你所关心的更长,更复杂。相反,我所说的是:

谁在乎为什么?你为什么还要烦恼呢?使用std::strlen - 这比你可以装备的任何东西都要好 - 然后继续解决更重要的问题 - 因为我确定这个不是最大的你的申请中的问题。