欣南特的short_alloc和对齐保证
Hinnant's short_alloc and alignment guarantees
我最近遇到了Howard Hinnant的short_alloc,这是我见过的自定义分配器的一个最好的例子。
但是,当我花更多的时间研究代码以将其集成到我的个人项目中时,我突然想到,提供基于堆栈的分配的arena
类可能并不总是返回正确对齐的内存。事实上,我担心只有第一个分配才能保证正确对齐(因为缓冲区本身有强制对齐),请参阅以下相关代码片段:
template <std::size_t N>
class arena
{
static const std::size_t alignment = 16;
alignas(alignment) char buf_[N];
char* ptr_;
//...
};
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n)
{
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
return static_cast<char*>(::operator new(n));
}
我可以想出几种方法来解决这个问题(以一些内存浪费为代价),最简单的方法是将allocate/deallocate
函数中的size
四舍五入为alignment
的倍数。
但在改变任何事情之前,我想确保我没有错过什么。。。
这段代码是在我的工具箱(现在位于<cstddef>
中)中有std::max_align_t
之前编写的。我现在把它写成:
static const std::size_t alignment = alignof(std::max_align_t);
在我的系统上,它与当前代码完全等效,但现在更具可移植性。这是new
和malloc
保证返回的对准。一旦你有了这个"最大对齐"的缓冲区,你就可以在其中放入任何一种类型的数组。但你不能对不同的类型使用相同的arena
(至少不能对不同类型有不同对齐要求)。出于这个原因,也许最好将arena
模板化在第二个size_t
上,它等于alignof(T)
。通过这种方式,您可以防止具有不同对齐要求的类型意外使用相同的arena
:
arena<N, alignof(T)>& a_;
假设来自arena
的每个分配具有相同的对准要求,并且假设缓冲器被最大程度地对准,则来自缓冲器的每个分配将针对T
被适当地对准。
例如,在我的系统alignof(std::max_align_t) == 16
上。具有这种对齐的缓冲区可以容纳以下阵列:
- 使用
alignof == 1
的类型 - 使用
alignof == 2
的类型 - 使用
alignof == 4
的类型 - 使用
alignof == 8
的类型 - 使用
alignof == 16
的类型
由于某些环境可能支持具有"超级校准"要求的类型,因此添加的安全预防措施是添加(比如在short_alloc
中):
static_assert(alignof(T) <= alignof(std::max_align_t), "");
如果你是超级偏执狂,你也可以检查alignof(T)
是2的幂,尽管C++标准本身保证这将永远是真的([basic.align]/p4)
更新
我仔细研究了这个问题,认为将请求的分配大小四舍五入到下一个alignment
(正如OP建议的那样)是最好的解决方案。我已经在我的网站上更新了"short_alloc"。
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n)
{
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
n = align_up(n);
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
return static_cast<char*>(::operator new(n));
}
对于知道不需要最大对齐分配的特殊情况(例如vector<unsigned char>
),可以简单地适当调整alignment
。也可以使short_alloc::allocate
通过alignof(T)
到arena::allocate
和assert(requested_align <= alignment)
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n, std::size_t requested_align)
{
assert(requested_align <= alignment);
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
n = align_up(n);
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
return static_cast<char*>(::operator new(n));
}
这会让你相信,如果你向下调整alignment
,你不会把它向下调整太远。
再次更新
由于这个极好的问题(我多年来一直忽略这个代码),我已经更新了这个分配器的描述和代码。
上一次更新中提到的对齐检查现在在编译时完成(编译时错误总是优于运行时错误,甚至断言)。
arena
和short_alloc
现在都是在对齐时模板化的,这样您就可以轻松地自定义您预期的对齐要求(如果猜测太低,则会在编译时发现)。此模板参数默认为alignof(std::max_align_t)
。
arena::allocate
函数现在看起来像:
template <std::size_t N, std::size_t alignment>
template <std::size_t ReqAlign>
char*
arena<N, alignment>::allocate(std::size_t n)
{
static_assert(ReqAlign <= alignment, "alignment is too small for this arena");
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
auto const aligned_n = align_up(n);
if (buf_ + N - ptr_ >= aligned_n)
{
char* r = ptr_;
ptr_ += aligned_n;
return r;
}
return static_cast<char*>(::operator new(n));
}
多亏了别名模板,这个分配器比以往任何时候都更容易使用。例如:
// Create a vector<T> template with a small buffer of 200 bytes.
// Note for vector it is possible to reduce the alignment requirements
// down to alignof(T) because vector doesn't allocate anything but T's.
// And if we're wrong about that guess, it is a comple-time error, not
// a run time error.
template <class T, std::size_t BufSize = 200>
using SmallVector = std::vector<T, short_alloc<T, BufSize, alignof(T)>>;
// Create the stack-based arena from which to allocate
SmallVector<int>::allocator_type::arena_type a;
// Create the vector which uses that arena.
SmallVector<int> v{a};
这不一定是此类分配器的最终决定。但希望这是一个坚实的基础,您可以在此基础上构建自定义分配器。
- 如何理解将半精度指针转换为无符号长指针和相关的内存对齐
- 如何创建一个QTableWidgetItem,用长文本右对齐,左边有省略号
- 我可以检测和更改 gcc/g++ 中结构的当前数据对齐设置吗?
- 64位机器上的C++内存对齐
- 为什么我可以将变量存储在不是其最小对齐方式的倍数的地址?
- 使 std::vector 分配对齐内存的现代方法
- C++ cout 将双精度对齐到精度 2 并正确对齐
- 在 64 位边界上对齐C++结构数组?
- 使用 g++7 构建的代码在访问未对齐的内存时崩溃
- 在 capnp FlatArrayMessageReader 的对齐内存缓冲区中接收 zmq 消息
- 是否值得对齐变量?
- 初始化派生结构的基部分/意外打包派生结构字段以对齐基结构的间隙
- 对齐和对齐的实际用例C++关键字
- 如何减少代码的运行时间以对齐文本?
- 指向包含对齐 C 结构C++类的 C 指针的对齐问题
- Linux C++ 中的页面对齐内存分配
- C++ 类层次结构中的"对齐"是什么意思?
- 运行时错误:引用绑定到类型"int"的未对齐地址0xbebebebebebebec6,这需要 4 个字节对齐 (stl_vector.h)
- 具有调整对齐方式的类型定义
- 对于堆上的页面对齐内存分配是否有任何优化或不同的 API?