c++ - 使用RowMajor和ColMajor数据排列的矩阵行求和的奇怪性能差异

标签 c++ performance gcc memory eigen

我决定检查矩阵中的数据排列如何影响简单操作的性能。
我写了一个简单的行求和算法,使用Eigen::Matrix作为数据存储。
虽然我认为RowMajor存储应该由于更好的缓存利用率而表现出更好的性能。

我将g++编译器与-O2选项一起使用,它给了我以下结果:

ColMajor:40791546 µsRowMajor:28790948 µs
没关系但是有了-O3,它给了我真正的奇怪的区别:

ColMajor:10353619 µsRowMajor: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/

相关文章:

c++ - 在 Windows Vista 中写入注册表

c++ - Visual C++ 静态初始值设定项异常行为

C++ 内存地址递增

c - 如何提高这个 Haskell 程序的性能?

ruby-on-rails - ActiveRecord 模型测试的性能

C++:将 uint 合并和拆分为 4 个字节的最快方法

c++ - Python 参数类型 C++ 签名

c - 奇怪的 gcc 警告行为

c - 将库中的专用 ELF 部分合并到应用程序专用 ELF 部分

c - 在 gcc 上编译时目标文件出错