我决定检查矩阵中的数据排列如何影响简单操作的性能。
我写了一个简单的行求和算法,使用Eigen::Matrix
作为数据存储。
虽然我认为RowMajor存储应该由于更好的缓存利用率而表现出更好的性能。
我将g++
编译器与-O2
选项一起使用,它给了我以下结果:
ColMajor:40791546 µs
RowMajor:28790948 µs
没关系但是有了-O3
,它给了我真正的奇怪的区别:
ColMajor:10353619 µs
RowMajor:28359348 µs
看起来ColMajor使用-O3
变得非常快。为什么从-O2
切换到-O3
会极大地改变性能?
我的CPU:英特尔i7-6700K,gcc版本:7.5.0-3ubuntu1~19.10
我的“基准”:
#include <iostream>
#include <vector>
#include <chrono>
#include "Eigen/Core"
template<typename DerivedMat, typename DerivedRes>
void runTest(const Eigen::MatrixBase<DerivedMat> &mat, Eigen::MatrixBase<DerivedRes> &res) {
const int64_t nRows = mat.rows();
const int64_t nCols = mat.cols();
for(int64_t row = 0; row < nRows; ++row){
for(int64_t col = 0; col < nCols; ++col){
res(row, 0) += mat(row, col);
}
}
}
const int64_t nRows = 300;
const int64_t nCols = 5000;
const int nAttempts = 20000;
template<int Alignment>
void bench() {
Eigen::Matrix<float, -1, -1, Alignment> mat(nRows, nCols);
srand(42);
mat.setRandom();
Eigen::VectorXf res(nRows);
res.setZero();
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
for(int iter = 0; iter < nAttempts; ++iter)
runTest(mat, res);
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
std::cout << "Elapsed " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[µs]" << std::endl;
}
int main() {
bench<Eigen::ColMajor>();
//bench<Eigen::RowMajor>();
return 0;
}
最佳答案
基于ColMajor
的循环在-O3
中要快得多,因为与基于RowMajor
的循环相比,GCC 7.5能够自动对其进行矢量化。您可以看到in the assembly code(L11
-labelled循环)。
自动矢量化为not performed by GCC in -O2
。
确实,提到的缓存效果与不适合缓存的大型矩阵特别相关,对于相对较小的矩阵,矢量化可能比缓存效率更重要。问题在于,GCC在简单归约的向量化方面存在一些困难。
您可以使用OpenMP指令(例如#pragma omp simd reduction(+:accumulatorVar)
)帮助他。另外,您可以使用Eigen提供的应进行矢量化的逐行求和(特别是对于连续数据)。
结果代码应该是所有先前代码中最快的。
Here是生成的汇编代码。
关于c++ - 使用RowMajor和ColMajor数据排列的矩阵行求和的奇怪性能差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62361744/