我对如何组织2D数据的内存布局犹豫不决。
基本上,我想要的是一个N * M 2D double
数组,其中N~M是数千(并且来自用户提供的数据)
我看到它的方式,我有两个选择:
double *data = new double[N*M];
或
double **data = new double*[N];
for (size_t i = 0; i < N; ++i)
data[i] = new double[M];
首选是我倾向于的。 我看到的主要优点是更短的新/删除语法,如果我正确安排访问,连续内存布局意味着在运行时相邻的内存访问,以及可能更好的矢量化代码性能(自动矢量化或使用矢量库,如vDSP或vecLib)
另一方面,在我看来,与分配一堆较小的连续内存相比,分配大量连续内存可能会失败/花费更多时间。与data[i][j]
data[i*M+j]
的优势
最常见/更好的方法是什么,主要是如果我尝试从性能的角度来看待它(尽管那些会有很小的改进,我很想知道哪个更能表现)
答案 0 :(得分:2)
在前两个选项之间,对于M
和N
的合理值,我几乎肯定会选择1.您跳过指针取消引用,如果您访问数据,则可以获得良好的缓存正确的顺序。
就您对尺寸的担忧而言,我们可以做一些背后的计算。
由于M
和N
数以千计,因此假设每个10000
为上限。然后消耗的总内存是
10000 * 10000 * sizeof(double) = 8 * 10^8
这大约是800 MB,虽然很大,但考虑到现代机器中的内存大小,这是非常合理的。
答案 1 :(得分:2)
如果N
和M
是常量,最好将静态声明所需的内存声明为二维数组。或者,您可以使用std::array
。
std::array<std::array<double, M>, N> data;
如果只有M
是常量,则可以使用std::vector
代替std::array
。
std::vector<std::array<double, M>> data(N);
如果M
不是常数,则需要执行一些动态分配。但是,std::vector
可用于为您管理内存,因此您可以围绕它创建一个简单的包装器。下面的包装器返回一个row
中间对象,以允许第二个[]
运算符实际计算vector
的偏移量。
template <typename T>
class matrix {
const size_t N;
const size_t M;
std::vector<T> v_;
struct row {
matrix &m_;
const size_t r_;
row (matrix &m, size_t r) : m_(m), r_(r) {}
T & operator [] (size_t c) { return m_.v_[r_ * m_.M + c]; }
T operator [] (size_t c) const { return m_.v_[r_ * m_.M + c]; }
};
public:
matrix (size_t n, size_t m) : N(n), M(m), v_(N*M) {}
row operator [] (size_t r) { return row(*this, r); }
const row & operator [] (size_t r) const { return row(*this, r); }
};
matrix<double> data(10,20);
data[1][2] = .5;
std::cout << data[1][2] << '\n';
解决您对性能的特殊关注:您希望单个内存访问的理由是正确的。你应该避免自己做new
和delete
(这是这个包装器提供的东西),如果数据更自然地被解释为多维的,那么在代码中会显示代码也更容易阅读。
你的第二种技术中显示的多次分配是次要的,因为它需要更多的时间,但它的优点是,如果你的系统碎片化,它可能会更频繁地成功(可用内存由较小的孔组成,而你没有免费的大块内存,足以满足单个分配请求)。但是多个分配还有另一个缺点,就是需要更多的内存来为每行的指针分配空间。
我的建议提供单一分配技术,无需明确调用new
和delete
,因为内存由vector
管理。同时,它允许使用二维语法[x][y]
来处理数据。因此,只要您有足够的内存来满足分配请求,它就可以提供单一分配的所有好处以及多分配的所有好处。
答案 2 :(得分:0)
考虑使用以下内容:
// array of pointers to doubles to point the beginning of rows
double ** data = new double*[N];
// allocate so many doubles to the first row, that it is long enough to feed them all
data[0] = new double[N * M];
// distribute pointers to individual rows as well
for (size_t i = 1; i < N; i++)
data[i] = data[0] + i * M;
我不确定这是否是一般做法,我只想出了这个。一些不足仍然适用于这种方法,但我认为它消除了大多数,例如能够访问像data[i][j]
这样的个人双打。