编译器优化是否可以消除在for循环条件中重复调用的函数?

时间:2013-11-13 08:21:08

标签: c++ compiler-optimization

我正在阅读哈希函数(我是一名中级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++)

我的问题是,由于这是一种显着提高性能的方法,编译器会自动为我们做这件事吗?我还不太了解编译器,但我很好奇这个问题的答案。在编写代码以使用较少的操作时,人们经常指出我所做的事情通常已经由编译器为我完成,所以我浪费时间而不是做内联函数等事情。我关心这个,因为我正在编写一个物理处理需要高效的游戏,以便让事情变得笨拙。

4 个答案:

答案 0 :(得分:3)

简短回答:有时候......

答案很长:

如果编译器可以根据循环本身确定key.length()是一个“常量”值,那么它将能够优化调用。这又取决于所用类的定义(在本例中为string,我们可以期待它“写得好”)。它还依赖于编译器的理解,即循环不会以改变key的方式改变key.length()

此功能的关键元素是函数为inline(或者是模板函数,inline是必需的,以允许它在不同的编译单元中被多次包含 - 或者在相同的源文件)和源代码在编译单元包含的头文件中。

当然,C ++标准中没有要求编译器执行此操作。每次调用该功能完全符合标准。

答案 1 :(得分:2)

key.length()移出循环是唯一安全的,如果它是一个纯函数 - 也就是没有副作用的函数。如果编译器可以检测到这是如此,那么我会说它可能会执行优化。

不幸的是,与Fortran不同,C ++没有标准方法来标记纯粹的东西。如果函数是inline(或内联函数),则编译器具有定义,并且可能为自己解决,或者至少消除函数调用。

将成员函数标记为const可以保证它不会影响当前实例,但原则上没有理由认为key.length()无法更改某个全局变量,所以我自己怀疑这个就足够了。

(在GCC和兼容的编译器(Clang,Intel)中有各种编译器特定的方式来声明一个纯函数 - 如__attribute__((pure))。也许可以试验这些并看看它们是否有任何区别?)

答案 2 :(得分:0)

有两个地方可以进行优化。

  1. 更聪明的编译器会反省短函数,看它们是否对if子句有影响。可能大多数编译器会inline调用,使其与你所写的内容相同。

  2. 在cpu级别上,与更“随机”if相比,分支预测也会(基本上)更快。关于SO的最重要问题之一显示了很好的硬核示例。

  3. 编辑: 我之前假设声明方法length() const就足够了。但它太弱了。方法仍然可以在不改变对象状态的情况下抛出随机的东西。但是我100%肯定,那些体面的编译器会像我描述的那样内省功能和方法。

答案 3 :(得分:0)

这是一个非常依赖于编译器的答案。确定的唯一方法是生成汇编程序并对其创建的内容进行戳(我建议学习如何使用编译器并尝试使用它,这非常有启发性*)。

优化并不明显 - 编译器可以执行该优化,但前提是确保.length()不会更改 - 即没有其他线程可以访问数据并且能够确定循环中的任何内容都不会改变.length()后者很容易检查,因为它是一个常量字符串,但前者不那么容易。话虽如此,我希望编译器假设在中等优化水平下它处于const状态。

* p故意;遗憾。