那可能是您问题的简化版本,但您使用的数据结构(“三星级”数组)几乎从不是您想要的。如果您创建的是类似于这里的密集矩阵,并为每个元素分配空间,则制作数百万个微小分配毫无优势。如果要使用稀疏矩阵,则通常需要像压缩稀疏行这样的格式。
如果数组是“矩形”的(或者我认为3D数组会是“盒状”的),并且所有行和列的大小都相同,则与分配单个内存块相比,此数据结构纯粹是浪费的。您执行了数百万个微小的分配,并为数百万个指针分配了空间,失去了内存局部性。
这个样板为动态3D数组创建了一个零成本抽象。 (好吧,几乎:存储基础一维std :: vector的长度和各个维度是多余的。)API使用
a(i,j,k)
作为等效于
a [i] [j] [k]
和
a.at(i,j,k)
作为带有边界检查的变体。
此API还提供一种选项,可以使用索引函数
f(i,j,k)
填充数组。如果调用
a.generate(f)
,它会设置每个
a(i,j,k)= f(i,j,k)
。理论上,这将减少内部循环中的偏移量计算,使其更快。API还可以将生成函数作为
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;
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;
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));
}
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) );
}
array3d( const array3d& ) = default;
array3d& operator= ( const array3d& ) = default;
array3d( array3d&& ) = default;
array3d& operator= (array3d&&) = default;
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);
}
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);
}
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());
}
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);
}
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 );
}
};
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);
cout << std::setprecision(8) << a.at(1234,4321,1) << endl;
return EXIT_SUCCESS;
}
值得注意的是,这段代码在技术上包含未定义行为:它假设有符号整数乘法溢出会产生负数,但实际上,如果程序在运行时请求了一些荒谬的内存量,编译器有权生成完全错误的代码。
当然,如果数组边界是常量,只需声明它们为
constexpr
并使用具有固定边界的数组即可。
不幸的是,每个新的C++程序员都首先学习
char** argv
,因为这让人们认为“二维”数组是指指向行的指针的“不规则”数组。
在现实世界中,这几乎从来不是最适合工作的数据结构。
mmap(2)
。 - Jonathon Reinhart