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

问题描述 投票:0回答:1

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

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

ColMajor:40791546 µsRowMajor:28790948 µs

很好。但是使用-O3,确实给我带来了非常奇怪的区别:

ColMajor:10353619 µsRowMajor:28359348 µs

[ColMajor似乎在使用-O3时变得非常快。为什么从-O2切换到-O3会极大地改变性能?

我的CPU:Intel 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;
}
c++ performance gcc memory eigen
1个回答
0
投票

基于ColMajor的循环在-O3中要快得多,因为与基于RowMajor的循环相比,GCC 7.5能够自动对其进行矢量化。您可以看到in the assembly code(标记为L11的循环)。自动向量化为not performed by GCC in -O2

确实,提到的高速缓存效果与不适合高速缓存的大矩阵]尤其相关,并且矢量化对于相对较小的矩阵比高速缓存效率更重要。问题在于,GCC在简单归约的向量化方面存在一些困难。您可以使用OpenMP指令(例如-O2)帮助他。或者,您可以使用Eigen提供的应进行矢量化的逐行求和(特别是对于连续数据)。结果代码应该是所有先前代码中最快的。#pragma omp simd reduction(+:accumulatorVar)是生成的汇编代码。

© www.soinside.com 2019 - 2024. All rights reserved.