未使用的功能会改变性能

Unused function changes performances

在尝试估计 push_backstd::inserter 之间的性能差异时,我 运行 遇到了一个非常奇怪的性能问题。

让我们考虑以下代码:

#include <vector>
using container = std::vector<int>;
const int size  = 1000000;
const int count = 1000;

#ifdef MYOWNFLAG
void foo(std::insert_iterator<container> ist)
{
    for(int i=0; i<size; ++i)
        *ist++ = i;
}
#endif

void bar(container& cnt)
{
    for(int i=0; i<size; ++i)
        cnt.push_back(i);
}
int main()
{
    container cnt;
    for (int i=0; i<count; ++i)
    {
        cnt.clear();
        bar(cnt);
    }
    return 0;
}

在这种情况下,无论是否定义了MYOWNFLAG,都不会调用函数foo。但是这个标志的值对性能有影响:

$ g++ -g -pipe -march=native -pedantic -std=c++11 -W -Wall -Wextra -Werror -O3 -o bin/inserter src/inserter.cc && time ./bin/inserter
./bin/inserter  4,73s user 0,00s system 100% cpu 4,728 total

$ g++ -g -pipe -march=native -pedantic -std=c++11 -W -Wall -Wextra -Werror -O3 -o bin/inserter src/inserter.cc -DMYOWNFLAG && time ./bin/inserter
./bin/inserter  2,09s user 0,00s system 99% cpu 2,094 total

请注意,如果我将 foo 的原型更改为使用 std::back_insert_iterator,我将获得与未设置标志类似的性能。

编译器的优化是怎么回事???

编辑

我使用 gcc 4.9.2 20150304(预发布版)

转载

首先,我将向您展示如何在没有垃圾功能的情况下实现这一目标的神奇技巧。然后我将向您展示为什么垃圾函数有效。所以技巧:

原来无效(注意我的机器大约快了两倍):

g++ -g -pipe -march=native -pedantic -std=c++11 -W -Wall -Wextra -Werror -O3 -o bin/inserter src/inserter.cc --param inline-unit-growth=200 && time ./bin/inserter
real    0m2.197s
user    0m2.200s
sys     0m0.000s

现在开始了(你的定义仍然无效):

g++ -g -pipe -march=native -pedantic -std=c++11 -W -Wall -Wextra -Werror -O3 -o bin/inserter src/inserter.cc --param inline-min-speedup=2 && time ./bin/inserter
real    0m1.114s
user    0m1.100s
sys 0m0.010s

注意:不同之处在于参数看起来很奇怪--param inline-min-speedup=2

下面我简单介绍一下调​​查:

  1. 快和慢有什么区别?在慢速版本中,我们确实在 bar() 中对 emplace_back_aux 进行了无效调用,当您的 foo 未被注释时,它会神奇地内联。所以我们可以得出结论,bar 非常热门,内联在这里是毁灭性的。很可能所有这些错误都与内联有关。

  2. 现在使用选项 -fdump-ipa-inline-details 让我们看看内联转储。您会看到不同的 time/size 注意事项。这很难阅读,我不想在这里粘贴所有细节。但研究此信息的一般结果:GCC 认为,模块大小的增长(以百分比表示)不值得估计加速。

  3. 怎么办?两种可能性:

    3.1。使用未使用的 foo 代码增加模块大小和整体加速估计,即使用像 insert_iterator 这样的正确类型来调用 emplace_back 并移动比率更大并达到内联限制(注意这种方式非常不稳定——在其他具有改进的内联算法的编译器版本中,一切都可能会爆炸,而且您还需要非常幸运才能猜出有效的代码。

    3.2。或移动内联限制。我用提供的参数对 GCC 说的是 "consider for inlining even big functions with less speedup please".

也就是。 GCC 中还有许多其他参数以及您可以使用它们执行的其他技巧。