C/C++:更快:for 循环或递增指针

C/C++: What's faster: a for loop, or incrementing a pointer

本文关键字:指针 循环 for C++ 更快      更新时间:2023-10-16

我想知道以下哪个代码段最快,假设目标是从somePointer指向的量numElements T类型的元素中读取并对其进行处理。我对循环结构本身的效率特别感兴趣,而不是对元素的处理方式。

第一名候选人

for (int i = 0; i < numElements; i++) {
    T val = somePointer[i];
    ... // Do something
}

第二名候选人

T* tempPointer = somePointer;
T* endPointer = somePointer + numElements;
while (tempPointer < endPointer) {
    T val = *tempPointer;
    ... // Do something
    tempPointer++;
}

当然,第一个候选人更清晰,更不容易出错。但是,如果它实际上被编译到它似乎会生成的代码中,我认为它会更慢。使用 for 循环需要每次循环迭代增加 i,并且在取消引用之前i * sizeOf(t)量与指向的地址偏移量somePointer。指针增量方法似乎只需要每个循环周期的一个加法操作,因此我相信它会更快。

但是,据我了解,编译器尝试使用 SIMD 指令尽可能矢量化for循环;如果编译器可以成功检测到for循环中的矢量化机会,但不能使用递增指针,那么for似乎是更快的选择。当然,据我所知,编译器正在检测for循环可以转换为指针增量的情况,并在矢量化之前进行转换,这将使它无关紧要。

简而言之,在现实世界的场景中,哪个更快?

从理论上讲,你的问题的答案是前一个更简单的代码。

一 实际实现不需要计算表达式的一部分,如果它可以推断出其 不使用值,并且不会产生所需的副作用(包括任何由 调用函数或访问易失性对象)。

这是 C 标准的引用,展示了编译器进行优化的能力。在这种情况下,不需要的表达式部分与int索引相关(可能应该是size_t)。

实际上,您的问题的答案也是前一种更简单的代码。您可能会惊喜地发现,今天的常见编译器可以非常轻松地执行您提到的优化(但更复杂)。但是,由于计算机系统的许多方面结合在一起可以构建更大的性能图景,因此无法给出其中哪一个会更快?我们需要了解有关您的实现的每个相关方面(CPU、内存、操作系统、编译器等)。

请参阅"它会优化吗?",了解GCC乐于优化的一些类似示例。这是循环不变计算优化的一种形式。确保在编译代码时启用了完全优化(通常-O3)。

但是,您需要考虑的不仅仅是优化。正如您提到的,前一种更简单的代码更易于阅读。这对于任何可能最终维护您的代码的人来说都很重要。

在考虑优化时,这里有一个方便的提示:你的老板会希望看到一些有效的东西,即使它太慢,早点而不是晚点。如果你没有老板,那就太好了!考虑到如果没有可以比较的东西,你就无法衡量优化的代码,但是......

编写清晰、简洁的代码以提高可维护性。如果你的老板(或你的团队,或者你自己,或者其他什么)决定什么时候完成它不够快,使用你的探查器来确定最重要的瓶颈在哪里,那么你应该对关注什么有一些想法......您将优化您的时间和代码。

完成优化后,请再次使用分析器来确定它是否有效。通过这种方式,您可以消除猜测可能产生的负面影响。

当今的常见编译器通常甚至可以根据探查器的输出执行优化。这种技术被称为"配置文件引导优化",可能值得研究......

作为一般规则,for 循环的最坏情况运行时间,以及像这样的 while 循环是 O(n)。也就是说,它会根据您拥有的元素数量线性增长。

在这种情况下,考虑哪一个更快几乎没有价值,因为它们本质上是相同的,假设您将在下面做什么

//Do something

是一样的。

在考虑程序的效率时,值得同时考虑运行时间和内存效率。

我认为在您的 for 循环/while 循环中写入的内容对于影响您的运行时间的因素更为重要。

希望这有帮助!

假设您在英特尔主板上使用GCC或MinGW或Cygwin。For 循环在英特尔主板中内置了对计数器递增的支持,如果您考虑第二个循环,在这种情况下,指针应随着它指向的数据类型的大小而递增,这将要求编译器将更多代码放入汇编代码中,最终会增加 CPU 开销,增加更多的 CPU 周期来完成您的代码,但在第一种情况下,编译器将生成汇编代码,以便将计数器变量 i 保留在寄存器本身中,使 CPU 易于比较并继续/中断循环。如果您在两个文件(一个.c和two.c)中编写两个代码并运行以下命令

gcc -S one.c
gcc -S two.c

查看汇编代码和 如果你了解 x86 汇编,你可能可以更清楚地理解我想说的话。我的理解是,如果你深入了解CPU和组装的工作原理,第一个循环会工作得更快。