优化循环的性能

时间:2015-06-26 21:06:44

标签: c++ performance c++11

我一直在分析我的代码中的瓶颈(下面显示的一个函数)被调用了几百万次。我可以使用提示来提高性能。 XXXs数字来自Sleepy

使用visual studio 2013,/O2和其他典型版本设置进行编译。

indicies通常为0到20个值,其他参数大小相同(b.size() == indicies.size() == temps.size() == temps[k].size())。

1:          double Object::gradient(const size_t j, 
2:                                  const std::vector<double>& b, 
3:                                  const std::vector<size_t>& indices, 
4:                                  const std::vector<std::vector<double>>& temps) const
5:  23.27s  {
6:              double sum = 0;
7:  192.16s     for (size_t k : indices)
8:  32.05s          if (k != j)
9:  219.53s             sum += temps[k][j]*b[k];
10:      
11: 320.21s     return boost::math::isfinite(sum) ? sum : 0;
13: 22.86s  }

有什么想法吗?

感谢提示家伙。以下是我从建议中得到的结果:

enter image description here

我觉得切换到cbegin()cend()产生如此大的影响很有意思。我想编译器并不像它那样聪明。我对碰撞很满意,但仍然很好奇,如果通过展开或矢量化在这里有更多的空间。

对于那些感兴趣的人,这里是isfinite(x)的基准:

boost::isfinite(x):
------------------------
SPEED: 761.164 per ms
TIME:  0.001314 ms
   +/- 0.000023 ms

std::isfinite(x):
------------------------
SPEED: 266.835 per ms
TIME:  0.003748 ms
   +/- 0.000065 ms

5 个答案:

答案 0 :(得分:4)

有两点可以说明:

(a)boost :: math :: isfinite花费的时间相对较长。如果可能,请尝试使用其他方式确定结果在有效范围内。

(b)将2D数组存储为嵌套向量不是禁食方式。将temp作为一维数组并将行大小作为参数传递可能会更快。要访问元素,您必须自己进行索引计算。但由于其中一个索引(j)在循环中是常量,因此可以在循环之前计算起始元素的索引,并在内部只增加索引1.这可能会使您获得一些显着的改进。

答案 1 :(得分:4)

如果您知道条件将被满足(在每次迭代中您将满足k == j),则消除条件并用简单的条件存储替换返回条件。

double sum = -(temps[j][j]*b[j]);
for (size_t k : indices)
     sum += temps[k][j]*b[k];
if (!std::isfinite(sum))
     sum = 0.0;
return sum;

基于范围的仍然是新的,不总是得到很好的优化。您可能还想尝试:

const auto it = cend(indices);
for (auto it = cbegin(indices); it != end; ++it) {
    sum += temps[*it][j]*b[*it];
}

并查看性能是否有所不同。

答案 2 :(得分:1)

如果您重新排列temps,我相信您会获得显着的性能提升。以下代码行

temps[k][j]*b[k]

迭代k的值是(至少我理解的)乘法(如果是一个(转置的)矩阵的乘法)。现在,访问元素temps[k][j]实际上涉及读取temps[k],取消引用它(指向已分配的向量的指针),然后读取其[j]元素。

除了使用vector<vector<double>之外,您还可以使用1维vector此外,由于此功能是您的瓶颈,因此您可以将数据存储在&#34;转置&#34;办法。以新格式访问temps[k][j]的方式变为temps[j * N + k]其中&#39; N&#39;是j的范围。 通过这种方式,您还可以更有效地利用缓存。

答案 3 :(得分:0)

我不确定它在您的解决方案中是否可行,但请考虑存储输入数据以使其满足CPU缓存。 除了隐式的begin()end()调用之外的其他成本,你可能最终会优化,缓存未命中问题会一直伤害你。不要在perf-critical代码中使用指向指针的指针。 还请尝试

for (const size_t& k : indices)

避免复制。但是,编译器可以为您优化。

答案 4 :(得分:0)

我尝试的第一个相对简单的优化是:

const std::vector<std::vector<double>>& temps

这可能是内存中的所有地方而不是缓存友好的,因为它基本上是一个动态数组的动态数组,其中存储的矩阵的每一行(或列)都有一个单独的动态数组,可能完全不同放在记忆中。

如果你可以创建一个“矩阵”类,它将数据存储在一个数组中(例如:单向量),这可能会有所帮助。

接下来我要尝试的是:

7:  192.16s     for (size_t k : indices)
8:  32.05s          if (k != j)
9:  219.53s             sum += temps[k][j]*b[k];

我们可以看到'j'在循环中从不改变,所以如果'temp'可以提前转换(如果你编写一个合适的Matrix类也很容易),你可以从左到右顺序访问内存右键并在单个缓存行中访问矩阵的多个组件。

最后但并非最不重要的是,您是否真的遇到无限或NaN与您正在使用的输入?如果是后者,我会看看是否可以在其他位置转移NaN检查以检查那些temps数组的有效性(前提是您多次重复使用相同的数组)。除非你已经在这里遇到过失败或正在使用真正的任务关键型软件,否则我会尝试将该检查转换为调试模式断言,并且只有在生产中遇到它时才处理这种边缘情况。