【问题标题】:Why is this C++ string length calculation function faster than this other one?为什么这个 C++ 字符串长度计算函数比另一个更快?
【发布时间】:2014-03-06 11:09:08
【问题描述】:

我们的讲师已经解释了这个计算字符串长度的函数......

int strlen_1(const char *str) {
    const char *temp = str;
    while(*temp != '\0') {
        temp++;
    }
    return temp - str;
}

...会比这个更快...

int strlen_03(const char *str) {
    int i;
    for (i = 0; *(str+i) != '\0'; i++);
    return i;

我认为他说这与算术计算有关,就像在第一个中所做的任何算术演算一样,但我无法理解,我认为它们处于同一水平。能否请您换句话解释一下原因?

PS。我确实理解指针,也能理解发生了什么,这就像将存储在“RAM 单元”中的数组元素逐个单步执行。

提前致谢。

【问题讨论】:

  • PS:微优化是个坏主意。如果你有性能问题,首先找出它使用最多 CPU 的地方
  • 开启优化并查看生成的程序集以清楚了解它们的不同之处。
  • 如果你的老师是正确的,那是因为第二个做了两个加法,而第一个只做了一个。然而,正如 chris 和 Ed Heal 所提到的,在您分析代码并发现实际的性能问题之前,担心这种级别的手动优化通常是没有意义的。无论如何,在优化代码中生成的程序集很可能是相同的。
  • 顺便说一句,虽然这个问题可能与 C 相当相关,但在 C++ 中,一个简单的 your_string.length() 通常会比上述任何一个都快得多,因为它是 O(1) 而不是 O(N )。
  • 重要的是循环内部的内容(包括 for 和 while 中的测试和更新部分)。第二个循环有两个添加,但第一个循环只有一个。

标签: c++ performance function pointers string-length


【解决方案1】:

暂时忽略优化,只看论文算法:

前者重复执行这个计算:

addr++

通过差值计算得到结果

addr1 - addr0

后者重复执行这些计算

addr0 + i
i++

通过返回值计算结果

i

换句话说,为了在计算最终结果时做一半的工作,循环中做了两倍的工作。

进入优化的 ASM,第一个在我的 clang 上的 -O3 处生成:

0x100000ee4:  cmpb   $0, 1(%rbx)
0x100000ee8:  leaq   1(%rbx), %rbx
0x100000eec:  sete   %al
0x100000eef:  testb  $1, %al
0x100000ef1:  je     0x100000ee4

第二个生成这个:

0x100000f09:  incl   %ebx
0x100000f0b:  cmpb   $0, (%rax)
0x100000f0e:  leaq   1(%rax), %rax
0x100000f12:  sete   %cl
0x100000f15:  testb  $1, %cl
0x100000f18:  je     0x100000f09

我省略了返回值的常量一次性计时器,因为它们不是循环复杂性的核心。优化器相当不错,注意唯一的主要区别是单:

0x100000f09:  incl   %ebx

这是你的i

【讨论】:

  • 而 CPU 并行性可能意味着 incl %ebx 实际上不涉及任何延迟。
  • 好点。所以它更多的是理论上而不是实践。也许如果我们有一个非常非常有限的硬件,那会很有趣。但你说这不涉及任何延迟?
【解决方案2】:

这是一种微优化,现代编译器可能最终会为两者生成相同的程序集,但对于未优化的版本,原因如下:

int strlen_1(const char *str) 
{
    const char *temp = str; // declare the iterator
    while(*temp != '\0')   // dereference the pointer
                           // test the iterator 
    {
        temp++; // increment the iterator
    }
    return temp - str; // pointer subtraction
}

对于一个长度为 N 的字符串,这会给你 3N + 2 次操作。

int strlen_03(const char *str) 
{
    int i; // declare your iterator
    for (i = 0; *(str+i) != '\0'; i++); // initialize the iterator
                                        // add i to str
                                        // dereference that pointer value
                                        // test it against \0
                                        // increment i
    return i;
}

对于相同的字符串,这会给你 4N + 2 次操作。

同样,现代编译器可能会为您解决此问题,即使对于大多数字符串(仅适用于 very长字符串)。

【讨论】:

  • 感谢 Zac,这是一个非常明确的答案。我想我终于明白了。顺便问一下,3N 或 4N 是什么意思? +2 是什么?请原谅我的无知。
  • @RamonBlanquer 表示伪操作数。你看到的 + 是一次性操作。第一个有两个一次性计时器(init 和减法评估)+ 每次迭代 3 个操作(deref、test、inc)。第二个有两个一次性计时器(init 和 i-eval)和每次迭代 4 个操作(add、deref、test、inc)。至少很多个月前我就是这样被教导的。
猜你喜欢
  • 2015-01-14
  • 2010-12-09
  • 1970-01-01
  • 2014-05-19
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
  • 1970-01-01
  • 2015-10-16
相关资源
最近更新 更多