封装的二维阵列与普通版本 - 访问速度

时间:2013-12-23 16:03:40

标签: c++ arrays optimization heap

这个问题是这个问题的继续:2-dimensional array on heap, which version is faster?

我已经定义了类似的内容:

class Array
{
    double *data;
    int X;
    int Y;

public:
    Array(int X, int Y, double init = 0) : X(X), Y(Y)
    {
        data = new double [X*Y];
        for (int i=0; i<X*Y; i++)
            data[i] = init;
    }
    ~Array() { delete[] data; }
    double *operator[] (int x) { return (data+x*Y); }
};

我想拥有一个具有二维可读性的连续数组的速度优势。我认为class Array会这样做,

Array arr(1000,1000);
arr[x][y] = n;

与普通版本(几乎)快速相同

double *arr = new double [1000*1000];
arr[x*1000+y] = n;

因为operator[]已定义为inline

但普通版本要快得多,封装的版本只有一点点快,因为真正的二维版本double **arr; ...; arr[x][y] = n; 不是真的,请参阅Edit2

这是正常的吗?我正在编译VC ++ 2010并进行优化。

使用vector请不要回答,我知道这种可能性,但我对这种行为的深层原因感兴趣...

修改

我已经阅读了评论,我的class Array进行了2次查找,我应该使用直接1次查找并返回对double的引用。我已经尝试了这个并且没有速度改进了,它完全一样。

我真的不明白为什么我的班级会进行2次查找:

Array arr(1000,1000);
arr[x][y] = n;

应该内联到:

(arr.data+x*arr.Y)[y] = n;

并进一步:

*((arr.data+x*arr.Y)+y) = n;

与...完全相同:

arr.data[x*arr.Y+y] = n; // the proposed 1 lookup access

我错了吗?

EDIT2

我又一次注意到,double **arr; arr[x][y] = n;解决方案的时间从1:47分到2:10分不等 - 以随机的方式。

所有其他解决方案:

  1. 封装class Array,如上所述
  2. 与提议的double &operator() (int x, int y)
  3. with plain double *arr; arr[x*Y+y] = n;
  4. 在1:44分钟左右快速相同并且始终保持不变。

3 个答案:

答案 0 :(得分:1)

如果我理解正确,你会问为什么使用2D和1D的速度似乎可以忽略不计。

在我看来,进行2D矩阵访问的最佳方法是使用以下内容。

double& operator()(const int row, const int col) inline{
    return data[X*row + col];
}

double operator()(const int row, const int col) inline const{
    return data[X*row + col];
}

这为您提供了参考和复制方法。

速度问题在于它高度依赖于机器的底层架构。

第一个问题是缓存大小。显然,缓存越大越好,1D版本应该更好地工作,因为2D通常作为连续内存对缓存更好。

同样在您的示例中,无论内存如何排序,第一次访问单个元素都会很慢,因为元素不在缓存中。但是,如果您多次访问该元素,或者同一区域(缓存行)中的元素,则加速应该更加明显。

第二个问题是矢量化。根据您正在进行的操作,特别是如果它们是数学操作(如添加等),它们将决定速度。如果您有一个具有SSE或AVX扩展的较新处理器,请确保编译器正在编译以使用这些功能,通常这在您设置优化时自动完成。您可能希望通过添加-march = native和-msse3或Windows等效项来确保。

另一个小优化是使X,Y const。这将使内联更加有效,但显然伴随着分配变得痛苦的缺点。

最后一句话:简介,看看你花费最多的时间并改进它。

答案 1 :(得分:1)

您无法获得性能优势,因为您仍然需要在包装版本中执行两次内存查找。仅在访问元素x * 1000 + y的1-d情况下的算术运算仅需要一次内存查找。你的包装版本返回一个指针,然后必须再次取消引用,这是缓慢的部分。

尝试将您的包装版本访问重新设为

inline double  operator()(int x, int y) const {return data[x*Y + y];}
inline double& operator()(int x, int y) {return data[x*Y + y];}

并呼叫为

arr(x,y) = n;

我很惊讶封装的那个比普通的二维数组快,因为它只能有更多的开销。

编辑:现在更多地关注这个问题,我发现你的解决方案实际上没有进行两次查找,因为你的重载[]运算符行为不同。请参阅我对原帖的评论。

答案 2 :(得分:0)

简单的另一个好处是维度是const,如果你不需要运行时维度,请尝试使用模板:

此外,不用担心内存管理 - 使用std::unique_ptr而不是double[]使用std::array

更适合
template <class T, size_t X, size_t Y>
class Array
{
    using custom_array=std::array<T,X*Y>;
    std::unique_ptr<custom_array> data;

public:
    Array() : data{new custom_array} {}
    Array(const Array& rhs) : data{new custom_array(*(rhs.data))} {}
    Array& operator=(const Array& rhs) {
         if (&rhs != this)
         {
            *data=*(rhs.data);
         }
         return *this;
    }
    ~Array() {}
    T& operator() (int x, int y) { return data->at(x*Y+y); }
    T operator() (int x, int y) const { return data->at(x*Y+y); }
};

用法:Array<double,1000,1000> A; double b=A(3,4);

对于苹果到苹果 - 将数据分配为std::array<double,X*Y>,但由于您需要堆分配,请将上述内容与unique_ptr一起使用。