C++ 批处理分配库
因此,我目前正在重构一个巨大的函数:C++ 批处理分配库,c++,dynamic-memory-allocation,applicative,C++,Dynamic Memory Allocation,Applicative,因此,我目前正在重构一个巨大的函数: int giant_function(size_t n, size_t m, /*... other parameters */) { int x[n]{}; float y[n]{}; int z[m]{}; /* ... more array definitions */ 当我找到一组具有离散功能的相关定义时,将它们分组为类定义: class V0 { std::unique_ptr<int[]> x; std::u
int giant_function(size_t n, size_t m, /*... other parameters */) {
int x[n]{};
float y[n]{};
int z[m]{};
/* ... more array definitions */
当我找到一组具有离散功能的相关定义时,将它们分组为类定义:
class V0 {
std::unique_ptr<int[]> x;
std::unique_ptr<float[]> y;
std::unique_ptr<int[]> z;
public:
V0(size_t n, size_t m)
: x{new int[n]{}}
, y{new float[n]{}}
, z{new int[m]{}}
{}
// methods...
}
这种方法不仅需要大量的手工(阅读:容易出错)记账,而且更大的缺点是它不可组合
如果我想在堆栈上有一个V1
值和一个W1
值,那就是
每个人分配一个幕后资源。更简单的是,我希望能够在一次分配中分配V1
和它所指向的资源,而我不能用这种方法
这使我最初采用了两步方法——一步计算需要多少空间,然后进行一次巨大的分配,然后再进行一次分配并初始化数据结构
class V2 {
int* x;
float* y;
int* z;
public:
static size_t size(size_t n, size_t m) {
return sizeof(V2) + n*sizeof(int) + n*sizeof(float) + m*sizeof(int);
}
V2(size_t n, size_t m, char** buf) {
x = (int*) *buf;
*buf += n*sizeof(int);
y = (float*) *buf;
*buf += n*sizeof(float);
z = (int*) *buf;
*buf += m*sizeof(int);
}
}
// ...
size_t total = ... + V2::size(n,m) + ...
char* buf = new char[total];
// ...
void* here = buf;
buf += sizeof(V2);
V2* v2 = new (here) V2{n, m, &buf};
然而,这种方法在远处重复了很多次,从长远来看,这会带来麻烦。退回一家工厂后,我们就摆脱了:
class V3 {
int* const x;
float* const y;
int* const z;
V3(int* x, float* y, int* z) : x{x}, y{y}, z{z} {}
public:
class V3Factory {
size_t const n;
size_t const m;
public:
Factory(size_t n, size_t m) : n{n}, m{m};
size_t size() {
return sizeof(V3) + sizeof(int)*n + sizeof(float)*n + sizeof(int)*m;
}
V3* build(char** buf) {
void * here = *buf;
*buf += sizeof(V3);
x = (int*) *buf;
*buf += n*sizeof(int);
y = (float*) *buf;
*buf += n*sizeof(float);
z = (int*) *buf;
*buf += m*sizeof(int);
return new (here) V3{x,y,z};
}
}
}
// ...
V3::Factory v3factory{n,m};
// ...
size_t total = ... + v3factory.size() + ...
char* buf = new char[total];
// ..
V3* v3 = v3factory.build(&buf);
仍然有一些重复,但是参数只得到一次输入。还有很多手工记账。如果我能用更小的工厂来建造这家工厂那就太好了
然后我的哈斯凯尔脑撞到了我。我正在实现一个应用函数。这完全可以更好
我所需要做的就是编写一些工具来自动计算大小并并行运行构建函数:
namespace plan {
template <typename A, typename B>
struct Apply {
A const a;
B const b;
Apply(A const a, B const b) : a{a}, b{b} {};
template<typename ... Args>
auto build(char* buf, Args ... args) const {
return a.build(buf, b.build(buf + a.size()), args...);
}
size_t size() const {
return a.size() + b.size();
}
Apply(Apply<A,B> const & plan) : a{plan.a}, b{plan.b} {}
Apply(Apply<A,B> const && plan) : a{plan.a}, b{plan.b} {}
template<typename U, typename ... Vs>
auto operator()(U const u, Vs const ... vs) const {
return Apply<decltype(*this),U>{*this,u}(vs...);
}
auto operator()() const {
return *this;
}
};
template<typename T>
struct Lift {
template<typename ... Args>
T* build(char* buf, Args ... args) const {
return new (buf) T{args...};
}
size_t size() const {
return sizeof(T);
}
Lift() {}
Lift(Lift<T> const &) {}
Lift(Lift<T> const &&) {}
template<typename U, typename ... Vs>
auto operator()(U const u, Vs const ... vs) const {
return Apply<decltype(*this),U>{*this,u}(vs...);
}
auto operator()() const {
return *this;
}
};
template<typename T>
struct Array {
size_t const length;
Array(size_t length) : length{length} {}
T* build(char* buf) const {
return new (buf) T[length]{};
}
size_t size() const {
return sizeof(T[length]);
}
};
template <typename P>
auto heap_allocate(P plan) {
return plan.build(new char[plan.size()]);
}
}
名称空间计划{
模板
结构应用{
常数A;
B常数B;
应用(常数A,常数B):A{A},B{B};
模板
自动生成(字符*buf,参数…参数)常量{
返回a.build(buf,b.build(buf+a.size()),args;
}
大小\u t大小()常量{
返回a.size()+b.size();
}
Apply(Apply const&plan):a{plan.a},b{plan.b}{
Apply(Apply const&&plan):a{plan.a},b{plan.b}{
模板
自动运算符()(U常量U,Vs常量…Vs)常量{
返回应用{*this,u}(vs.);
}
自动运算符()()常量{
归还*这个;
}
};
模板
结构升降机{
模板
T*构建(char*buf,Args…Args)常量{
返回新的(buf)T{args…};
}
大小\u t大小()常量{
返回大小f(T);
}
提升(){}
提升(提升常数&){}
提升(提升常数&&{}
模板
自动运算符()(U常量U,Vs常量…Vs)常量{
返回应用{*this,u}(vs.);
}
自动运算符()()常量{
归还*这个;
}
};
模板
结构数组{
尺寸常数长度;
数组(大小\u t长度):长度{length}{}
T*构建(char*buf)常量{
返回新的(buf)T[length]{};
}
大小\u t大小()常量{
返回sizeof(T[长度]);
}
};
模板
自动堆分配(P计划){
return plan.build(新字符[plan.size()]);
}
}
现在,我可以简单地说明我的课程:
class V4 {
int* const x;
float* const y;
int* const z;
public:
V4(int* x, float* y, int* z) : x{x}, y{y}, z{z} {}
static auto plan(size_t n, size_t m) {
return plan::Lift<V4>{}(
plan::Array<int>{n},
plan::Array<float>{n},
plan::Array<int>{m}
);
}
};
V4类{
int*常数x;
浮动*常数;
int*常数z;
公众:
V4(int*x,float*y,int*z):x{x},y{y},z{z}
静态自动计划(大小n,大小m){
返回计划::提升{}(
计划::数组{n},
计划::数组{n},
计划::数组{m}
);
}
};
并一次性使用:
V4* v4;
W4* w4;
std::tie{ ..., v4, w4, .... } = *plan::heap_allocate(
plan::Lift<std::tie>{}(
// ...
V4::plan(n,m),
W4::plan(m,p,2*m+1),
// ...
)
);
V4*V4;
W4*W4;
std::tie{…,v4,w4,…}=*计划::堆分配(
计划::提升{}(
// ...
V4::平面图(n,m),
W4::平面图(m,p,2*m+1),
// ...
)
);
它并不完美(在其他问题中,我需要添加代码来跟踪析构函数,并让heap\u allocate
返回一个调用所有函数的std::unique\u ptr
),但在我进一步深入兔子洞之前,我认为我应该检查一下已有的art
据我所知,现代编译器可能足够聪明,可以认识到V0
中的内存总是一起分配/解除分配,并为我批处理分配
如果没有,这个想法(或其变体)是否已有实现,用于使用applicative functor批处理分配?首先,我想就您的解决方案的问题提供反馈:
int
和float
在您的系统上共享相同的对齐方式,您的特定用例可能是“好的”。但是试着在混合中加入一些double
,就会出现UB。您可能会发现您的程序由于未对齐的访问而在ARM芯片上崩溃
new(buf)T[length]{}很不幸,代码>是错误的。简而言之:标准允许编译器保留给定存储的初始y
字节供内部使用。您的程序无法在y>0
的系统上分配这个y
字节(是的,这些系统显然存在;VC++据称是这样做的)
必须为y
进行分配是不好的,但使数组placement new无法使用的是,在实际调用placement new之前,无法确定y
有多大。真的没有办法用它来处理这个案子
解决方案:
alignof(T)-1
字节。将每个缓冲区的开始与std::Align
对齐
static_assert
)。注意,对非平凡破坏的支持增加了对异常安全的需求。如果一个缓冲区的构造引发异常,则需要销毁先前构造的子缓冲区。当建造单个
V4* v4;
W4* w4;
std::tie{ ..., v4, w4, .... } = *plan::heap_allocate(
plan::Lift<std::tie>{}(
// ...
V4::plan(n,m),
W4::plan(m,p,2*m+1),
// ...
)
);
#include <cstddef>
#include <memory>
#include <vector>
#include <tuple>
#include <cassert>
#include <type_traits>
#include <utility>
// recursion base
template <class... Args>
class buffer_clump {
protected:
constexpr std::size_t buffer_size() const noexcept { return 0; }
constexpr std::tuple<> buffers(char*) const noexcept { return {}; }
constexpr void construct(char*) const noexcept { }
constexpr void destroy(const char*) const noexcept {}
};
template<class Head, class... Tail>
class buffer_clump<Head, Tail...> : buffer_clump<Tail...> {
using tail = buffer_clump<Tail...>;
const std::size_t length;
constexpr std::size_t size() const noexcept
{
return sizeof(Head) * length + alignof(Head) - 1;
}
constexpr Head* align(char* buf) const noexcept
{
void* aligned = buf;
std::size_t space = size();
assert(std::align(
alignof(Head),
sizeof(Head) * length,
aligned,
space
));
return (Head*)aligned;
}
constexpr char* next(char* buf) const noexcept
{
return buf + size();
}
static constexpr void
destroy_head(Head* head_ptr, std::size_t last)
noexcept(std::is_nothrow_destructible<Head>::value)
{
if constexpr (!std::is_trivially_destructible<Head>::value)
while (last--)
head_ptr[last].~Head();
}
public:
template<class... Size_t>
constexpr buffer_clump(std::size_t length, Size_t... tail_lengths) noexcept
: tail(tail_lengths...), length(length) {}
constexpr std::size_t
buffer_size() const noexcept
{
return size() + tail::buffer_size();
}
constexpr auto
buffers(char* buf) const noexcept
{
return std::tuple_cat(
std::make_tuple(align(buf)),
tail::buffers(next(buf))
);
}
void
construct(char* buf) const
noexcept(std::is_nothrow_default_constructible<Head, Tail...>::value)
{
Head* aligned = align(buf);
std::size_t i;
try {
for (i = 0; i < length; i++)
new (&aligned[i]) Head;
tail::construct(next(buf));
} catch (...) {
destroy_head(aligned, i);
throw;
}
}
constexpr void
destroy(char* buf) const
noexcept(std::is_nothrow_destructible<Head, Tail...>::value)
{
tail::destroy(next(buf));
destroy_head(align(buf), length);
}
};
template <class... Args>
class buffer_clump_storage {
const buffer_clump<Args...> clump;
std::vector<char> storage;
public:
constexpr auto buffers() noexcept {
return clump.buffers(storage.data());
}
template<class... Size_t>
buffer_clump_storage(Size_t... lengths)
: clump(lengths...), storage(clump.buffer_size())
{
clump.construct(storage.data());
}
~buffer_clump_storage()
noexcept(noexcept(clump.destroy(nullptr)))
{
if (storage.size())
clump.destroy(storage.data());
}
buffer_clump_storage(buffer_clump_storage&& other) noexcept
: clump(other.clump), storage(std::move(other.storage))
{
other.storage.clear();
}
};
class V5 {
// macro tricks or boost mpl magic could be used to avoid repetitive boilerplate
buffer_clump_storage<int, float, int> storage;
public:
int* x;
float* y;
int* z;
V5(std::size_t xs, std::size_t ys, std::size_t zs)
: storage(xs, ys, zs)
{
std::tie(x, y, z) = storage.buffers();
}
};
int giant_function(size_t n, size_t m, /*... other parameters */) {
V5 v(n, n, m);
for(std::size_t i = 0; i < n; i++)
v.x[i] = i;
int giant_function(size_t n, size_t m, /*... other parameters */) {
buffer_clump_storage<int, float, int> v(n, n, m);
auto [x, y, z] = v.buffers();