在C ++中分配大内存块

时间:2018-08-29 01:49:42

标签: c++ memory-management

我正在尝试为C ++浮点值的3D矩阵分配一个大的存储块。它的尺寸是44100x2200x2。这应该恰好占用44100x2200x2x4字节的内存,大约为7.7gb。我在带有Ubuntu的64位x86机器上使用g ++编译代码。当我使用htop查看进程时,我看到内存使用量增加到32gb,并立即被杀死。我的记忆计算有误吗?

这是我的代码:

#include <iostream>

using namespace std;
int main(int argc, char* argv[]) {
  int N = 22000;
  int M = 44100;
  float*** a = new float**[N];
  for (int m = 0; m<N; m+=1) {
    cout<<((float)m/(float)N)<<endl;
    a[m] = new float*[M - 1];
    for (int n = 0; n<M - 1; n+=1) {
      a[m][n] = new float[2];
    }
  }
}

编辑:我的计算不正确,我分配的内存接近38gb。我现在修复了分配15gb的代码。

#include <iostream>

using namespace std;
int main(int argc, char* argv[]) {
  unsigned long  N = 22000;
  unsigned long  M = 44100;
  unsigned long blk_dim = N*(M-1)*2;
  float* blk = new float[blk_dim];
  unsigned long b = (unsigned long) blk;

  float*** a = new float**[N];
  for (int m = 0; m<N; m+=1) {
    unsigned long offset1 = m*(M - 1)*2*sizeof(float);
    a[m] = new float*[M - 1];
    for (int n = 0; n<M - 1; n+=1) {
      unsigned long offset2 = n*2*sizeof(float);
      a[m][n] = (float*)(offset1 + offset2 + b);
    }
  }
}

3 个答案:

答案 0 :(得分:10)

您忘记了一个维度,以及分配内存的开销。所显示的代码在三维空间中分配内存的效率非常低,从而导致过多的开销。

float*** a = new float**[N];

这将分配大约22000 * sizeof(float **),大约176kb。可以忽略不计。

a[m] = new float*[M - 1];

这里的单个分配将用于44099 * sizeof(float *),但是您将获得其中的22000。 22000 * 44099 * sizeof(float *),或大约7.7gb的额外内存。这是您停止计数的地方,但是代码尚未完成。还有很长的路要走。

a[m][n] = new float[2];

这是8个字节的单个分配,但是此分配将完成22000 * 44099次。那是 另一个 7.7gb耗尽了水。现在,大约需要分配15多个应用程序所需的内存。

但是每次分配都不免费,并且new float[2]要求的更多超过8个字节。 C ++库必须在内部跟踪每个单独分配的块,以便delete可以回收它。最简单的基于堆列表的链接列表实现需要一个前向指针,一个后向指针以及分配的块中有多少字节的计数。假设不需要为对齐目的而填充任何内容,那么在64位平台上,每个分配至少要占用24字节的开销。

现在,由于您的第三个维度进行了22000 * 44099的分配,第二个维度进行了22000的分配,而第一个维度进行了一个分配:如果我用手指指望,这将需要(22000 * 44099 + 22000 +1)* 24,或另外22 GB的内存,只是为了消耗最简单的基本内存分配方案的开销。

如果我正确地进行了数学运算,那么使用最简单,可能的堆分配跟踪功能,现在我们需要多达38 GB的RAM。您的C ++实现可能会使用稍微复杂一些的堆分配逻辑,但开销会更大。

摆脱new float[2]。计算矩阵的大小,并new单个7.7gb块,然后计算其余指针应指向的位置。另外,为矩阵的第二个维度分配一个内存块,并为第一个维度计算指针。

您的分配代码应准确执行三个new语句。一个用于第一维指针,一个用于第二维指针。还有涉及构成第三维的大量数据的另一种方法。

答案 1 :(得分:3)

仅舍入一个已经给出的答案,下面的示例基本上是对how to create a contiguous 2D array上给出的答案的扩展,并说明了对new[]的三个调用的用法。

优点是您保留了通常与三重指针一起使用的[][][]语法(尽管我强烈建议不要像这样使用“ 3星”来编写代码,但是我们有足够的能力)。缺点是为指针分配了更多的内存,并为数据分配了单个内存池。

#include <iostream>

template <typename T>
T*** create3DArray(unsigned pages, unsigned nrows, unsigned ncols, const T& val = T())
{
    T*** ptr = nullptr;  // allocate pointers to pages
    T** ptrMem = nullptr;
    T* pool = nullptr;
    try 
    {
        ptr = new T**[pages];  // allocate pointers to pages
        ptrMem = new T*[pages * nrows]; // allocate pointers to pool
        pool = new T[nrows*ncols*pages]{ val };  // allocate pool

        // Assign page pointers to point to the pages memory,
        // and pool pointers to point to each row the data pool
        for (unsigned i = 0; i < pages; ++i, ptrMem += nrows)
        {
            ptr[i] = ptrMem;
            for (unsigned j = 0; j < nrows; ++j, pool += ncols)
                ptr[i][j] = pool;
        }
        return ptr;
     }
     catch(std::bad_alloc& ex)
     {
         // rollback the previous allocations
        delete [] pool;
        delete [] ptrMem;
        delete [] ptr;
        throw ex; 
    }
}

template <typename T>
void delete3DArray(T*** arr)
{
    delete[] arr[0][0]; // remove pool
    delete[] arr[0];  // remove the pointers
    delete[] arr;     // remove the pages
}

int main()
{
    double ***dPtr = nullptr;
    try 
    {
        dPtr = create3DArray<double>(4100, 5000, 2);
    }
    catch(std::bad_alloc& )
    {
        std::cout << "Could not allocate memory";
        return -1;
    }
    dPtr[0][0][0] = 10;  // for example
    std::cout << dPtr[0][0][0] << "\n";
    delete3DArray(dPtr);  // free the memory
}

Live Example

答案 2 :(得分:1)

这可能是问题的简化版本,但是您正在使用的数据结构(“三星”数组)几乎永远不会是您想要的数据结构。如果您要像这样创建一个密集矩阵并分配每个元素都有足够的空间,进行数百万个微小的分配完全没有优势。如果您想要稀疏矩阵,通常需要像压缩稀疏行这样的格式。

如果数组是“矩形”的(或者我想一个3-D数组将是“ boxy”的),并且所有行和列的大小都相同,那么与分配单个内存块相比,这种数据结构纯粹是浪费。您将执行数百万个微小的分配,为数百万个指针分配空间,并且会丢失内存的局部性。

此样板为动态3-D数组创建了零成本的抽象。 (好吧,几乎:存储基础一维std::vector的长度和各个维都没有必要。)API使用a(i, j, k)相当于a[i][j][k]和{{1 }}作为带有边界检查的变体。

此API还可以选择使用索引a.at(i,j,k)的函数填充数组。如果调用f(i,j,k),它将设置每个a.generate(f)。从理论上讲,这种强度减少了内部循环中的偏移量计算,从而使其速度更快。 API也可以将生成函数作为a(i,j,k) = f(i,j,k)传递给构造函数。随意扩展它。

array3d<float>(M, N, P, f)

当然,如果数组边界是常量,则只需声明#include <cassert> #include <cstddef> #include <cstdlib> #include <functional> #include <iomanip> #include <iostream> #include <vector> using std::cout; using std::endl; using std::ptrdiff_t; using std::size_t; /* In a real-world implementation, this class would be split into a * header file and a definitions file. */ template <typename T> class array3d { public: using value_type = T; using size_type = size_t; using difference_type = ptrdiff_t; using reference = T&; using const_reference = const T&; using pointer = T*; using const_pointer = const T*; using iterator = typename std::vector<T>::iterator; using const_iterator = typename std::vector<T>::const_iterator; using reverse_iterator = typename std::vector<T>::reverse_iterator; using const_reverse_iterator = typename std::vector<T>::const_reverse_iterator; /* For this trivial example, I don’t define a default constructor or an API * to resize a 3D array. */ array3d( const ptrdiff_t rows, const ptrdiff_t cols, const ptrdiff_t layers ) { const ptrdiff_t nelements = rows*cols*layers; assert(rows > 0); assert(cols > 0); assert(layers > 0); assert(nelements > 0); nrows = rows; ncols = cols; nlayers = layers; storage.resize(static_cast<size_t>(nelements)); } /* Variant that initializes an array with bounds and then fills each element * (i,j,k) with a provided function f(i,j,k). */ array3d( const ptrdiff_t rows, const ptrdiff_t cols, const ptrdiff_t layers, const std::function<T(ptrdiff_t, ptrdiff_t, ptrdiff_t)> f ) { const ptrdiff_t nelements = rows*cols*layers; assert(rows > 0); assert(cols > 0); assert(layers > 0); assert(nelements > 0); nrows = rows; ncols = cols; nlayers = layers; storage.reserve(static_cast<size_t>(nelements)); for ( ptrdiff_t i = 0; i < nrows; ++i ) for ( ptrdiff_t j = 0; j < ncols; ++j ) for ( ptrdiff_t k = 0; k < nlayers; ++k ) storage.emplace_back(f(i,j,k)); assert( storage.size() == static_cast<size_t>(nelements) ); } // Rule of 5: array3d( const array3d& ) = default; array3d& operator= ( const array3d& ) = default; array3d( array3d&& ) = default; array3d& operator= (array3d&&) = default; /* a(i,j,k) is the equivalent of a[i][j][k], except that the indices are * signed rather than unsigned. WARNING: It does not check bounds! */ T& operator() ( const ptrdiff_t i, const ptrdiff_t j, const ptrdiff_t k ) noexcept { return storage[make_index(i,j,k)]; } const T& operator() ( const ptrdiff_t i, const ptrdiff_t j, const ptrdiff_t k ) const noexcept { return const_cast<array3d&>(*this)(i,j,k); } /* a.at(i,j,k) checks bounds. Error-checking is by assertion, rather than * by exception, and the indices are signed. */ T& at( const ptrdiff_t i, const ptrdiff_t j, const ptrdiff_t k ) { bounds_check(i,j,k); return (*this)(i,j,k); } const T& at( const ptrdiff_t i, const ptrdiff_t j, const ptrdiff_t k ) const { return const_cast<array3d&>(*this).at(i,j,k); } /* Given a function or function object f(i,j,k), fills each element of the * container with a(i,j,k) = f(i,j,k). */ void generate( const std::function<T(ptrdiff_t, ptrdiff_t, ptrdiff_t)> f ) { iterator it = storage.begin(); for ( ptrdiff_t i = 0; i < nrows; ++i ) for ( ptrdiff_t j = 0; j < ncols; ++j ) for ( ptrdiff_t k = 0; k < nlayers; ++k ) *it++ = f(i,j,k); assert(it == storage.end()); } /* Could define a larger API, e.g. begin(), end(), rbegin() and rend() from the STL. * Whatever you need. */ private: ptrdiff_t nrows, ncols, nlayers; std::vector<T> storage; constexpr size_t make_index( const ptrdiff_t i, const ptrdiff_t j, const ptrdiff_t k ) const noexcept { return static_cast<size_t>((i*ncols + j)*nlayers + k); } // This could instead throw std::out_of_range, like STL containers. constexpr void bounds_check( const ptrdiff_t i, const ptrdiff_t j, const ptrdiff_t k ) const { assert( i >=0 && i < nrows ); assert( j >= 0 && j < ncols ); assert( k >= 0 && k < nlayers ); } }; // In a real-world scenario, this test driver would be in another source file: constexpr float f( const ptrdiff_t i, const ptrdiff_t j, const ptrdiff_t k ) { return static_cast<float>( k==0 ? 1.0 : -1.0 * ((double)i + (double)j*1E-4)); } int main(void) { constexpr ptrdiff_t N = 2200, M = 4410, P = 2; const array3d<float> a(N, M, P, f); // Should be: -1234.4321 cout << std::setprecision(8) << a.at(1234,4321,1) << endl; return EXIT_SUCCESS; } 并使用具有固定边界的数组即可。

不幸的是,每个新的C ++程序员都首先了解constexpr,因为这使人们认为“二维”数组是指向行的“指针”数组。

在现实世界中,这几乎永远不是工作的最佳数据结构。