【问题标题】:Loop reordering across function boundaries in STL AlgorithmsSTL 算法中跨函数边界的循环重新排序
【发布时间】:2010-12-17 01:41:01
【问题描述】:

为简单起见,假设我有一个由 N 矩阵组成的向量,每个矩阵 M 行。我正在使用 STL std::accumulate 来计算所有矩阵的总和。我传递了一个二进制函子,它接受两个矩阵(通过引用)并返回它们的总和(通过引用)。全面披露:我正在使用 libstdc++ 并行模式。在函子内部,我逐行循环以计算总和。

虽然每个矩阵都太大而无法放入缓存中,但一行非常适合。因此,重新排序循环将是有利的,以便外部循环索引M 行,而内部循环索引N 矩阵。除了定义内联函子之外,我还能做些什么来鼓励这种跨功能边界循环重新排序。我当然可以重构代码,但理想情况下我希望保持使用 STL 算法提供的简单结构。如果有特定于 gcc 的东西,我也不介意。

我实际上并不是在处理矩阵,这只是一个例子,但同样的问题结构也适用。主要问题是性能问题。解释实际场景太麻烦了,但核心问题是:STL 的累积需要在嵌套循环之间进行排序,这对缓存不是很友好,因为它试图在移动到下一个对象之前完成两个对象的相加。单个对象太大而无法保存在缓存中,但它的一部分可以。因此,如果一次计算“加法”一个“部分”(在所有对象上),则可以加快执行速度。手动重新排序循环可以显着提高 FLOPS。但理想情况下,我希望编译器进行重新排序,以便我可以在 STL 级别(尽可能)进行编码。所以我正在寻找技巧来做到这一点。

【问题讨论】:

  • 我不明白这个问题。你有性能问题吗?你能告诉我们你有什么吗?
  • @wilhelmtell 我添加了更多细节。希望现在清楚一点。
  • 我赞成到目前为止收到的所有三个建议。我仍在寻找一种方法来提示编译器做正确的事情。因为正确的东西因缓存大小而异。可能的解决方案是使用预处理器进行条件编译。
  • 都是很好的答案,基本上都指向同一件事。但是我只能接受一个,所以我选择了最明确的一个。

标签: c++ stl compiler-optimization


【解决方案1】:
class Matrix;
class Row;
struct SumNRow {
  int _rowidx;
//  Row _tempRow; //For return by reference left out for simplicity
  SumNRow(int iRowIdx): _rowIdx(iRowIdx) {}
  Row operator(const Matrix & iMarix1, const Matrix iMatrix2) {
    return iMarix1[_rowIdx] + iMatrix2[_rowIdx];
  }
};

template<class MatrixIterator>
void sum(const MatrixIterator & iMarixStart, const MatrixIterator & iMatrixEnd, Matrix & oMarix) {
  for (int i = 0; i < iMarixStart->rowCount(); ++i) {
    oMarix[i]=std::accumulate(iMarixStart, iMatrixEnd, SumNRow(i));
  }
}

【讨论】:

  • 这基本上就是 wilhelmtell 所描述的。
【解决方案2】:

我无法想象编译器会解决这个问题,除非所有内容都被内联并且 M 和 N 是常量。即使那样,这也将是一个延伸。

为了保持 STL 算法风格,在累加上使用 foreach M 并让函子对一行求和。

【讨论】:

  • Hi @Lou M 和 N 可以作为编译时间常数。这将导致缺乏灵活性,但我可以忍受。我完全按照您提到的去做,即在 accumulate 函子内调用 STL 循环构造。但它似乎并没有重新排序。是否应该重新排序取决于矩阵(对象)的大小和缓存大小,我认为编译器无法找出这一点。如果有一种方法(模板魔法)可以强制重新排序,那就太好了。如果我错过了您的建议,请告诉我。
  • @srean:您似乎误解了这个答案。我相信这里的重新排序很清楚,我的回答重复了完全相同的想法。
  • Lou Franco:很抱歉错过了你的答案并重复了同样的事情。
  • @Basilevs 是的,没错。我不明白 Lou Franco 也提出了同样的修复建议。
【解决方案3】:

编写一个新算法,或将内容包装在 for 循环或 std::for_each() 调用中。这将比寻找适应std::accumulate() 的方法容易得多。我认为这里唯一的其他选择是向库引入一个新的抽象级别,即超越迭代器。编写一个新算法或引入一个额外的循环会更容易。

【讨论】:

  • 这里不是算法而是数据结构是次优的来源,即不可缓存的大对象的向量。我可以将对象拆分为现在是几个向量,每个向量都包含对象的一部分,这是一个重大的重组。问题是这种改变可能是正确的,也可能不是正确的,这取决于机器的缓存大小。理想情况下,我希望通过在目标 m/c 上重新编译来处理这些并发症。现在看来,这是一个不切实际的目标,即没有简单的解决方案。我希望编译器会优化它。
猜你喜欢
  • 2011-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-27
  • 2017-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多