c ++重载括号运算符比直接访问数组成员慢

c++ overloaded parentheses operator slower than direct access to array member

目前,我正在尝试弄清楚为什么当我使用重载的括号运算符 (//first multiplication) 时,我的原始矩阵-矩阵-乘法速度较慢(0.7 秒)。如果我不使用它们 (//second multiplication) 并且乘法直接访问 class 成员数组 data_ 它大约快两倍(0.35 秒)。我使用 Matrix.h 中定义的自己的矩阵 class。

为什么速度会有如此显着的差异?我的复制构造函数有问题吗?在调用重载运算符函数时是否有太多 "overhead" 可以证明这种性能损失是合理的?

还有一个问题/奇怪的行为:当你交换最里面的两个循环(xinner)时,乘法(当然)非常慢,但是现在这两个乘法几乎花费相同的时间(7 秒)。为什么在这种情况下他们需要相同的时间,但之前有 ~50% 的性能差异。

编辑:程序编译如下:g++ -c -std=c++0x -O3 -DNDEBUG

非常感谢您的帮助!

我的主要功能如下所示:

#include "Matrix.h"
int main(){
    Matrix m1(1024,1024, 2.0);
    Matrix m2(1024,1024, 2.5);
    Matrix m3(1024,1024);

    //first multiplication
    for(int y = 0; y < 1024; ++y){
        for(int inner = 0; inner < 1024; ++inner){
            for(int x = 0; x < 1024; ++x){
                m3(y,x) += m1(y, inner) * m2(inner, x);
            }
        }
    }

    //second multiplication
    for(int y = 0; y < 1024; ++y){
        for(int inner = 0; inner < 1024; ++inner){
            for(int x = 0; x < 1024; ++x){
                m3.data_[y*1024+x] += m1.data_[y*1024+inner]*m2.data_[inner*1024+inner];
            }
        }
    }
}

这里是 Matrix.h 的部分:

class Matrix{

public:
    Matrix();
    Matrix(int sizeY, int sizeX);
    Matrix(int sizeY, int sizeX, double init);
    Matrix(const Matrix & orig);
    ~Matrix(){delete[] data_;}
    double & operator() (int y, int x);
    double operator() (int y, int x) const;

    double * data_;

private:    
    int sizeX_;
    int sizeY_;

}

这里是 Matrix.h

的实现
Matrix::Matrix()
    : sizeX_(0),
    sizeY_(0),
    data_(nullptr)
{ }

Matrix::Matrix(int sizeY, int sizeX)
    : sizeX_(sizeX),
    sizeY_(sizeY),
    data_(new double[sizeX*sizeY]())
{
    assert( sizeX > 0 );
    assert( sizeY > 0 );
}

Matrix::Matrix(int sizeY, int sizeX, double init)
    : sizeX_(sizeX), 
    sizeY_(sizeY)
{   
    assert( sizeX > 0 );
    assert( sizeY > 0 );
    data_ = new double[sizeX*sizeY];
    std::fill(data_, data_+(sizeX_*sizeY_), init);
}

Matrix::Matrix(const Matrix & orig)
    : sizeX_(orig.sizeX_),
    sizeY_(orig.sizeY_)
{
    data_ = new double[orig.sizeY_*orig.sizeX_];
    std::copy(orig.data_, orig.data_+(sizeX_*sizeY_), data_);
}


double & Matrix::operator() (int y, int x){
    assert( x >= 0 && x < sizeX_);
    assert( y >= 0 && y < sizeY_);
    return data_[y*sizeX_ + x];
}

double Matrix::operator() (int y, int x) const {
    assert( x >= 0 && x < sizeX_);
    assert( y >= 0 && y < sizeY_);
    return data_[y*sizeX_ + x];
}

EDIT2: 原来我对 //second multiplication 使用了错误的数组访问。我将其更改为 m3.data_[y*1024+x] += m1.data_[y*1024+inner]*m2.data_[inner*1024+x];,现在两个乘法运算时间相同。 非常感谢您的帮助!

我怀疑不同之处在于在 operator() 版本中,sizeX_ 不是 const,这可能会阻止编译器优化某些东西,即将 sizeX_ 重复加载到寄存器中。尝试在 class 定义中声明 sizeX_ 和 sizeY_ const。

您应该按照评论中的建议在 header 中内联函数。

我认为你的两个版本计算的不是同一件事:

在第一个你有:

m3(y,x) += m1(y, 内部) * m2(内部, x);

但在第二个你有

m3.data_[y*1024+x] += m1.data_[y*1024+inner]*m2.data_[inner*1024+inner];

第二个可以分解 inner out 而不是 inner * (1024 + 1) 可以优化第一个不能的多种方式。

两个版本的输出是什么?它们匹配吗?

编辑:另一位回答者非常正确地建议 class 中的维度不是常量将对 table 进行一些优化;在第一个版本中,编译器不知道大小是 2 的幂,因此它使用通用乘法,但在第二个版本中,它知道其中一个操作数是 1024(不只是一个常数,而是一个 编译时间常量)所以它可以使用快速乘法(左移2的幂)。

(对我之前关于 NDEBUG 的回答表示歉意:我打开了该页面一段时间,所以没有看到您对编译行的编辑。)