爲什麼函數在C++文件中的位置會影響其性能?特別是在下面給出的例子中,我們有兩個相同的函數,它們具有不同的一致的性能特徵。人們如何去研究這一點並確定性能如此不同的原因?爲什麼函數在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是相同的。它們執行相同的累加操作並編譯成相同的彙編指令。
- 每個測試迭代都有一個預熱期,然後才能開始嘗試並消除升溫高速緩存的任何問題。
當這是編譯和運行我們得到呈現出比B顯著慢以下的輸出:
[[email protected]:~/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)
)一個仍然較慢比b:
[[email protected]:~/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移動的定義之上的)結果倒置,並且變得比乙快!
[[email protected]:~/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)
- 該代碼使用gcc 4.8,在Ubuntu 13.04,Ubuntu 13.10和Ubuntu 12.04.03上編譯爲gcc 4.8.1。
- 在Intel Sandy Bridge i7-2600和Intel Xeon X5482 cpus上觀察到的效果。
爲什麼會發生這種情況?有什麼工具可以調查這樣的事情?
它們有可能在不同的頁面上結束,這會導致額外的工作?我覺得奇怪的是CPU時間在系統測量中,而不是用戶。這意味着用戶代碼的運行不是花費時間,而是代表進程的一些操作系統級別的事情。 –
作爲一個在黑暗中完整的鏡頭,我會建議乙會議更熱,因爲會議運行的第一個結果...(編輯:哦,你倒...) –
@DaveS我相信時間全部在用戶空間域中。循環加熱循環(測量之前)應該充分加熱緩存和分支預測。 – Shane