【问题标题】:Memory layout : 2D N*M data as pointer to N*M buffer or as array of N pointers to arrays内存布局:2D N*M 数据作为指向 N*M 缓冲区的指针或作为指向数组的 N 个指针的数组
【发布时间】:2014-08-07 01:31:22
【问题描述】:

我正在犹豫如何组织我的 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]相比语法更短的优势

最常见/更好的方法是什么,主要是如果我尝试从性能的角度来看它(即使这些会是小的改进,我很想知道哪个会更好)。

【问题讨论】:

  • NM 是常量吗?
  • 为什么不创建一个包含std::vector 的类,重载operator()(size_t i, size_t j) 以供访问。
  • 不知道为什么用 C 标记,好像malloc 和类似的答案会被接受或任何东西。
  • @jxh : 不,N 和 M 来自用户提供的数据
  • @ThoAppelsin :如前所述,我关心的只是内存布局和性能影响,而不是精确的语法。任何直接处理指针的语言的任何解决方案都适合我(也可以使用伪代码)。在这种情况下,C 语言似乎很重要。重新标记...

标签: c++ c arrays pointers data-structures


【解决方案1】:

在前两个选项之间,对于 MN 的合理值,我几乎肯定会选择选项 1。您跳过指针取消引用,如果您以正确的顺序访问数据,您将获得很好的缓存。

就您对尺寸的担忧,我们可以做一些粗略的计算。

由于MN 有数千个,假设每个都是10000 作为上限。那么你消耗的总内存是

 10000 * 10000 * sizeof(double) = 8 * 10^8

这大约是 800 MB,虽然很大,但考虑到现代机器的内存大小,这是相当合理的。

【讨论】:

  • 好的,所以您的推理与我的相同,都是首选第一个选择。感谢您的意见。
【解决方案2】:

如果NM 是常量,最好将所需的内存静态声明为二维数组。或者,您可以使用std::array

std::array<std::array<double, M>, N> data;

如果只有M 是一个常量,您可以使用std::array 中的std::vector

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';

在解决您对性能的特别关注时:您想要单个内存访问的理由是正确的。但是,您应该避免自己执行newdelete(这是此包装器提供的),并且如果数据更自然地解释为多维,那么在代码中显示将使代码更容易也请阅读。

您的第二种技术中显示的多次分配效果较差,因为它会花费更多时间,但它的优点是如果您的系统碎片化(可用内存由较小的孔组成,并且您没有足够大的空闲内存块以满足单个分配请求)。但是多次分配还有另一个缺点,就是需要更多的内存来为每一行的指针分配空间。

我的建议是提供单一分配技术,无需显式调用newdelete,因为内存由vector 管理。同时,它允许使用二维语法[x][y] 寻址数据。因此,它提供了单次分配的所有好处以及多重分配的所有好处,前提是您有足够的内存来满足分配请求。

【讨论】:

  • +1,虽然取决于MN 的大小,但尝试使用std::array(这是堆栈分配的)可能会导致问题。
  • std::array 并不总是“堆栈分配”。
  • @aschepler:嗯……很有趣。你到底是什么意思?
  • M 和 N 不是常数。相应地编辑了我的帖子。您只是提出了一个类似于@ThoApplesin 的包装器,但没有讨论我的主要观点,它超出了语法
  • @Olotiar:包装器非常出色,因为管理内存的工作已为您完成,而我的不同,因为它像您一样是单个分配,但还提供了您的 [x][y] 语法需要。
【解决方案3】:

考虑使用类似下面的东西:

// 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] 这样的单个双打等等。

【讨论】:

  • 其实很有趣的技术。很想听听人们成功应用类似的东西。但是不要讨论我的主要观点,它超出了语法范围
猜你喜欢
  • 2011-01-12
  • 2011-05-10
  • 1970-01-01
  • 2015-04-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-02
  • 1970-01-01
相关资源
最近更新 更多