在配置文件引导优化之后嵌套for循环更快但具有更高的缓存未命中

时间:2014-04-09 20:44:15

标签: c++ gcc optimization profiling

我有一个程序,其核心是一个以

形式的二维数组
std::vector<std::vector< int > > grid

这是一个简单的双循环,有点像这样:

for(int i=1; i<N-1; ++i)
    for(int j=1; j<N-1; ++j)
        sum += grid[i][j-1] + grid[i][j+1] + grid[i-1][j] + grid[i+1][j] + grid[i][j]*some_float;

使用g++ -O3它运行速度非常快,但为了进一步优化,我使用callgrind进行了分析,看到L1缓存约为37%,LL为33%,考虑到随机性,这是很多但不太令人惊讶是计算的本质。所以我做了一个配置文件引导的优化a la

g++ -fprofile-generate -O3 ...
./program
g++ -fprofile-use -O3 ...

并且程序运行速度提高了约48%!但令人费解的是:缓存未命中甚至增加了! L1数据缓存未命中率为40%,LL相同。

怎么会这样?循环中没有条件可以优化预测并且缓存未命中甚至更高。但它更快。

修改:好的,这是sscce:http://pastebin.com/fLgskdQG。使用N来玩不同的运行时。编译通过

g++ -O3 -std=c++11 -sscce.cpp

on linux下的gcc 4.8.1。

使用上述命令进行配置文件引导优化。 Callgrind的内容是用g ++ -g开关和valgrind --tool=callgrind --simulate-cache=yes ./sscce

完成的

1 个答案:

答案 0 :(得分:1)

我注意到使用或不使用PGO生成的汇编代码之间只有一个显着差异。没有PGO sum变量从寄存器溢出到内存,每次内循环迭代一次。这个写入内存并将其加载回来可能在理论上放慢了速度。幸运的是,现代处理器通过存储到负载转发来优化它,因此减速并不是那么大。英特尔的优化手册仍然不建议将浮点变量溢出到内存中,尤其是当它们通过长延迟操作(如浮点乘法)计算时。

这里真正令人费解的是为什么GCC需要PGO以避免寄存器溢出内存。它是足够的未使用的浮点寄存器,即使没有PGO编译器也可以从单个源文件获得正确优化所需的所有信息...

这些不必要的加载/存储操作不仅解释了为什么PGO代码更快,而且还解释了为什么它增加了缓存未命中率。如果没有PGO寄存器总是溢出到内存中的相同位置,那么这种额外的内存访问会增加内存访问次数和缓存命中次数,同时它不会改变缓存未命中数。使用PGO,我们可以减少内存访问,但缓存未命中数相同,因此它们的百分比会增加。