封装的二维数组与普通版本相比——访问速度

Encapsulated 2-dimensional array versus plain version - access speed

本文关键字:访问 速度 版本 二维数组 封装      更新时间:2023-10-16

这个问题是这个问题的延续:堆上的二维数组,哪个版本更快?

我定义了这样的东西:

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); }
};

我想要一个具有2维可读性的连续数组的速度优势。我以为class Array会用做到这一点

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

(几乎)与普通版一样快

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

因为CCD_ 2被定义为CCD_。

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

这正常吗?我正在VC++2010上编译,并在.上进行了优化

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

编辑

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

我真的不明白为什么我的类要进行两次查找:

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

我错了吗?

第2版

我再次计时,注意到double **arr; arr[x][y] = n;溶液有不同的时间,从1:47分钟到2:10分钟不等,是随机的。

所有其他解决方案:

  1. 封装class Array
  2. 提出类似double &operator() (int x, int y)
  3. 带普通double *arr; arr[x*Y+y] = n;

实际上在1:44分钟左右是相同的快,并且总是恒定的。

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

尝试将你的包装版本访问重塑为

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;

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

编辑:现在,随着对这个问题的深入研究,我发现您的解决方案实际上不进行两次查找,因为您的重载[]运算符的行为不同。请参阅我对原始帖子的评论。

如果我理解正确,你会问为什么使用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常量。这将使内联更加有效,但很明显,它的缺点是赋值变得很痛苦。

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

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

此外,不必担心内存管理-这将更适合使用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);

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