我正在为一个小型游戏引擎开发内存池。
主要用途是作为隔离存储;池包含特定类型和大小的对象。目前,池可用于存储任何内容,但分配将在特定大小的块中完成。大部分内存需求将立即分配,但如果需要协助调整(几乎固定大小),可以启用“过度增长”。
问题是,在考虑内存对齐时,我开始有些偏执。我只习惯8位处理器上的原始内存管理,其中所有内容都是字节对齐的。
我让用户(我)指定所需的块大小,这些块在隔离存储的情况下将是我将要存储在其中的对象的大小。
当前的方法是分配一大块内存blocks * (desired_size + header_size)
并将对象放入其中,每个块都有一个标头;对象显然会直接位于此标题后面。
在我的场景中,关于内存对齐,我需要考虑什么?
我到目前为止得出的答案是,只要desired_size
代表 n 字节对齐的数据;标题由编译器以及实际数据正确对齐和打包,存储在块中的所有内容都将 n - 字节对齐。
n是平台所需的任何边界。我目前的目标是x86,但我不想在我的代码中对平台做出任何假设。
我使用过的一些资源:
修改
上传的小样本代码可能对将来困扰我的任何人有所帮助here。
答案 0 :(得分:7)
保证malloc
的分配与编译器提供的任何类型对齐,因此对齐任何对象[*]。
当您的标头的对齐要求小于实施的最大对齐要求时,存在危险。那么它的大小可能不是最大值的倍数。对齐,所以当你尝试使用buf + header_size
作为指向 具有最大值的东西的指针时。对齐,它是错位的。就C而言,这是未定义的行为。在英特尔它的工作,但速度较慢。在某些ARM上,它会导致硬件异常。在某些ARM上,它默默地给出了错误的答案。因此,如果您不想在代码中对平台做出假设,则必须处理它。
基本上我有三个技巧可以确保您的标题不会导致错位:
int
作为填充,使其成为8倍,而不仅仅是4倍”。union
,以及您实际想要使用的结构。在C中运行良好,但如果您的标题对于工会会员资格无效,则在C ++中会遇到问题。或者,您可以将header_size
定义为不sizeof(header)
,但要将该大小四舍五入为2的“2足够大”的倍数。如果你浪费了一点内存,那就这样吧,你可以随时拥有一个“可移植性标题”来定义这种事物,这种方式不是纯粹与平台无关的,而是可以很容易地适应新平台。
[*]有一个常见的例外是SIMD类型过大。由于它们是非标准的,仅仅因为它们而对每个分配进行16对齐是浪费的,所以它们会被手动放在一边,你需要特殊的分配功能。
答案 1 :(得分:2)
您的编译器已经对齐将存储在池中的对象和结构的成员。使用适合您的体系结构的默认打包,对于32位内核,通常为8。您只需确保从池中生成的地址相应对齐。在32位操作系统上应该是8字节的倍数。
当对象跨越CPU缓存行边界时,错误对齐对象可能非常昂贵。跨越两个缓存行的双倍需要读取或写入的时间长达三倍。
答案 2 :(得分:2)
我也调整了内存对齐方式。首先要理解的是每个对象都有自己的内存对齐要求,这个要求(最多)是对象本身的大小。幸运的是,它通常更小。
有两种类型的工具可以帮助编写正确的内存分配器(C ++ 0x,它们可以在大多数STL的std::tr1
中找到):
std::alignment_of
将对齐作为编译时值std::aligned_storage
根据参数与他们两个人合作,你就会得到你需要的东西。
然而,您的设计稍微偏离基础,而不是太多介意,因为您的标题通常不会与存储的对象具有相同的对齐要求。
template <class T, size_t N>
struct Block
{
// Header bits
typedef std::aligned_storage<
N*sizeof(T),
std::alignment_of<T>::value
>::type buffer_type;
buffer_type mBuffer;
};
两个注释:
Block
本身可能有不同的对齐方式,但无关紧要sizeof
进行对齐,即使有点浪费也能保证正常工作最后,你也可以在没有所有这些和一些指针算术的情况下成功,但是因为它被提供....
答案 3 :(得分:1)
使用http://msdn.microsoft.com/en-us/library/bb982233.aspx - std :: alignment_of。这样可以确保对于您分配到池中的每个T,您将知道对齐并确保它适合。我不会假装知道/理解您将用于将此信息转换为对齐保证的实际逻辑,但它确实存在且可供使用。
答案 4 :(得分:1)
据我所知,boost :: pool基于alexandrescu的“小对象分配器”,在他的“现代c ++设计”一书中解释过。我必须读书(因为你正在写这些东西也是为了学习)