编译器优化能否消除在 for 循环的条件中反复调用的函数?
Can compiler optimization elminate a function repeatedly called in a for-loop's conditional?
我正在阅读有关哈希函数的信息(我是一名中级CS学生),并遇到了这个:
int hash (const string & key, int tableSize) {
int hasVal = 0;
for (int i = 0; i < key.length(); i++)
hashVal = 37 * hashVal + key[i];
.....
return hashVal;
}
我正在查看这段代码,并注意到如果在 for 循环中而不是每次执行此操作时都调用 key.length() 会更快:
int n = key.length();
for (int i = 0; i < n; i++)
我的问题是,由于这是稍微提高性能的明显方法,编译器会自动为我们执行此操作吗?我对编译器还不太了解,但我很好奇这个问题的答案。在编写代码以使用较少的操作时,人们经常指出我做的事情通常已经由编译器为我完成,所以我在浪费时间,但做诸如内联函数之类的事情。我关心这一点,因为我正在编写一个游戏,其中物理处理需要高效,这样事情就不会感觉笨拙。
简短的回答:它有时可以...
长答案:
如果编译器可以根据循环本身确定 key.length() 是一个"常量"值,那么它将能够优化调用。这反过来又取决于所用类的定义(在本例中为 string
,我们可以预期它是"写得很好"的)。它还依赖于编译器的理解,即循环不会以改变key.length()
的方式更改key
。
要做到这一点的关键要素是函数是inline
的(或者是一个模板函数,inline
需要允许它多次包含在不同的编译单元中 - 或在同一源文件中可用),并且源代码位于编译单元包含的头文件中。
当然,C++标准中没有要求编译器这样做。每次调用函数完全符合标准。
只有当key.length()
是一个纯函数时,才安全地将它移出循环 - 也就是说,一个没有副作用的函数。如果编译器可以检测到这种情况,那么我会说它很可能会执行优化。
不幸的是,与Fortran不同,C++没有标准的方式来将某些东西标记为纯净。如果函数是inline
的(或可内联的),那么编译器就有了定义,并且可能可以自己解决,或者至少消除函数调用。
将成员函数标记为 const
可以保证它不会影响当前实例,但原则上没有理由key.length()
不能更改某些全局变量,所以我怀疑这本身就足够了。
(有多种编译器特定的方法来声明函数纯 - 如GCC和兼容编译器(Clang,Intel)中的__attribute__((pure))
。也许尝试一下这些,看看它们是否有任何不同?
这是一个非常依赖于编译器的答案。确定的唯一方法是生成汇编程序并戳一下它创建的内容(我建议学习如何使用编译器执行此操作并尝试一下,这很有启发性*)。
优化不是那么明显 - 编译器可以执行该优化,但前提是它确定.length()
不会更改 - 即没有其他线程可以访问数据,并且它能够确定循环内的任何内容都不会改变.length()
后者很容易检查,因为它是一个常量字符串, 但前者则不然。话虽如此,我希望编译器假设它在中等优化级别上无处不在。
*双关语故意;对不起。
有两个地方可以进行优化。
-
更聪明的编译器会内省短函数,看看它们是否对
if
子句有影响。可能大多数编译器会inline
调用,使其等效于您编写的内容。 -
在CPU级别,分支预测也将使其(大大)比更"随机"
if
更快。关于SO
的最重要的问题之一展示了很好的硬核例子。
编辑:我之前假设声明方法 length() const
就足够了。但它太弱了。方法仍然可以在不更改对象状态的情况下抛出随机内容。但是,我 100% 确定,像我描述的那样,体面的编译器可以执行内省函数和方法。
- 循环中的条件:为什么每次都调用strlen(),而vector.size()只调用一次
- 访问条件类成员的方法不仅在被调用时才编译
- 在运行时有条件地删除类成员或跳过调用该成员对象的构造函数
- 如何使用 SFINAE 在方法调用中有条件地定义变量?
- 条件加倍时的递归调用
- "is defined"宏检查及其调用必须处于不同的条件下吗?
- 调用一个小函数两次(例如在if条件和主体中)比将结果存储在局部变量中更可取
- 在循环条件中调用const vector size()似乎缺少优化
- 对基构造函数的条件调用
- 在解压缩可变参数模板时避免"recursive"函数调用,直到运行时条件
- 如何避免参数数量依赖于条件的调用分支?
- 使用条件模板C 调用功能
- 现代C++编译器是否能够避免在某些条件下两次调用常量函数
- 构造函数的条件调用
- 使用模板调用条件函数
- 调用以条件循环(c++)
- 在 CPP 中将函数调用定时为 if 语句条件
- 中断已处于条件变量等待调用中的提升线程
- 在条件调用new时调用delete
- 使用条件调用模板函数