【发布时间】:2013-10-27 08:12:40
【问题描述】:
为什么函数在 c++ 文件中的位置会影响其性能?特别是在下面给出的示例中,我们有两个相同的函数,它们具有不同的、一致的性能配置文件。如何对此进行调查并确定性能为何如此不同?
这个例子非常简单,因为我们有两个函数:a 和 b。每个都在一个紧密的循环中运行多次,并优化 (-O3 -march=corei7-avx) 和定时。代码如下:
#include <cstdint>
#include <iostream>
#include <numeric>
#include <boost/timer/timer.hpp>
bool array[] = {true, false, true, false, false, true};
uint32_t __attribute__((noinline)) a() {
asm("");
return std::accumulate(std::begin(array), std::end(array), 0);
}
uint32_t __attribute__((noinline)) b() {
asm("");
return std::accumulate(std::begin(array), std::end(array), 0);
}
const size_t WARM_ITERS = 1ull << 10;
const size_t MAX_ITERS = 1ull << 30;
void test(const char* name, uint32_t (*fn)())
{
std::cout << name << ": ";
for (size_t i = 0; i < WARM_ITERS; i++) {
fn();
asm("");
}
boost::timer::auto_cpu_timer t;
for (size_t i = 0; i < MAX_ITERS; i++) {
fn();
asm("");
}
}
int main(int argc, char **argv)
{
test("a", a);
test("b", b);
return 0;
}
一些显着的特点:
- 函数 a 和 b 相同。它们执行相同的累加操作并编译成相同的汇编指令。
- 每次测试迭代都有一个预热期,然后计时开始尝试消除预热缓存的任何问题。
当它编译并运行时,我们得到以下输出,显示 a 明显比 b 慢:
[me@host:~/code/mystery] make && ./mystery
g++-4.8 -c -g -O3 -Wall -Wno-unused-local-typedefs -std=c++11 -march=corei7-avx -I/usr/local/include/boost-1_54/ mystery.cpp -o mystery.o
g++-4.8 mystery.o -lboost_system-gcc48-1_54 -lboost_timer-gcc48-1_54 -o mystery
a: 7.412747s wall, 7.400000s user + 0.000000s system = 7.400000s CPU (99.8%)
b: 5.729706s wall, 5.740000s user + 0.000000s system = 5.740000s CPU (100.2%)
如果我们颠倒两个测试(即调用test(b) 然后test(a))a 仍然比 b 慢:
[me@host:~/code/mystery] make && ./mystery
g++-4.8 -c -g -O3 -Wall -Wno-unused-local-typedefs -std=c++11 -march=corei7-avx -I/usr/local/include/boost-1_54/ mystery.cpp -o mystery.o
g++-4.8 mystery.o -lboost_system-gcc48-1_54 -lboost_timer-gcc48-1_54 -o mystery
b: 5.733968s wall, 5.730000s user + 0.000000s system = 5.730000s CPU (99.9%)
a: 7.414538s wall, 7.410000s user + 0.000000s system = 7.410000s CPU (99.9%)
如果我们现在反转 C++ 文件中函数的位置(将 b 的定义移到 a 之上),结果会反转并且 a 变得比 b 快!
[me@host:~/code/mystery] make && ./mystery
g++-4.8 -c -g -O3 -Wall -Wno-unused-local-typedefs -std=c++11 -march=corei7-avx -I/usr/local/include/boost-1_54/ mystery.cpp -o mystery.o
g++-4.8 mystery.o -lboost_system-gcc48-1_54 -lboost_timer-gcc48-1_54 -o mystery
a: 5.729604s wall, 5.720000s user + 0.000000s system = 5.720000s CPU (99.8%)
b: 7.411549s wall, 7.420000s user + 0.000000s system = 7.420000s CPU (100.1%)
所以本质上,无论位于 c++ 文件顶部的哪个函数都比较慢。
您可能有的问题的一些答案:
- 编译的代码对于 a 和 b 是相同的。已检查拆卸。 (感兴趣的朋友:http://pastebin.com/2QziqRXR)
- 代码是在 ubuntu 13.04、ubuntu 13.10 和 ubuntu 12.04.03 上使用 gcc 4.8、gcc 4.8.1 编译的。
- 在 Intel Sandy Bridge i7-2600 和 Intel Xeon X5482 cpu 上观察到的效果。
为什么会发生这种情况?有哪些工具可用于调查此类问题?
【问题讨论】:
-
它们是否有可能出现在不同的页面上,这会导致额外的工作?我觉得奇怪的是 CPU 时间在系统测量中,而不是用户。这意味着不是用户代码的运行需要时间,而是代表进程的一些操作系统级别的事情。
-
作为在黑暗中的完整镜头,我建议 b 会话由于 a 会话首先运行而更加热身......(编辑:哦,你倒了...... )
-
@DaveS 我相信时间都在用户空间领域。循环预热循环(测量之前)应该充分加热缓存和分支预测。
-
哎呀,误读了输出,你是对的时间。虽然我仍然很好奇它是如何调用函数的,因为我能看到的区别是函数在哪里。也许尝试将函数 a/b 移动到它们自己的对象上?就是看看有没有效果。
-
我认为这可能会发生,如果巧合的是,其中一个不能完全保存在缓存中,而另一个可以。
标签: c++ performance gcc micro-optimization