我正在尝试为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);
}
}
}
答案 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
}
答案 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
,因为这使人们认为“二维”数组是指向行的“指针”数组。
在现实世界中,这几乎永远不是工作的最佳数据结构。