矩阵行求和与 RowMajor 和 ColMajor 数据排列的奇怪性能差异

Strange performance difference of matrix row summation with RowMajor and ColMajor data arrangement

我决定检查矩阵中的数据排列如何影响简单操作的性能。 我使用 Eigen::Matrix 作为数据存储编写了简单的行求和算法。 我认为 RowMajor 存储应该展示更好的性能,因为缓存利用率更高。

我使用了带有 -O2 选项的 g++ 编译器,结果如下:

上校:40791546 µs 主要行:28790948 µs

还不错。但是 -O3 它给了我非常奇怪的区别:

上校:10353619 µs 主要行:28359348 µs

看起来 ColMajor 在 -O3 上变得非常快。为什么从 -O2 切换到 -O3 会如此显着地改变性能?

我的CPU:intel i7-6700K,gcc版本:7.5.0-3ubuntu1~19.10

我的"benchmark":

#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 中要快得多,因为 GCC 7.5 能够自动对其进行矢量化,而不是基于 RowMajor 的循环。您可以看到 in the assembly codeL11 标记的循环)。 自动矢量化为 not performed by GCC in -O2.

实际上,提到的缓存效果尤其适用于 不适合缓存的大矩阵 ,对于相对较小的矩阵,矢量化可能比缓存效率更重要。问题是 GCC 在简单缩减的向量化方面有一些困难。 您可以帮助他使用 OpenMP 指令,例如 #pragma omp simd reduction(+:accumulatorVar)。或者,您可以使用 Eigen 提供的按行求和,该求和应该被矢量化(尤其是对于连续数据)。 生成的代码应该是所有先前代码中最快的。 Here 是生成的汇编代码。