[英]Custom allocator and memory alignment
我正在尝试根据此处的要求实现自定义分配器以使用标准容器: https://en.cppreference.com/w/cpp/named_req/Allocator
我目前正在尝试实现线性分配器,但我很难使用 memory alignment。
在我分配一块 memory 之后,我想知道块中每个 object 之间需要多少填充以优化 CPU 读/写。 我不确定地址 alignment 是否应该是整除的
sizeof(T)
alignof(T)
我在不同的地方阅读了不同的答案。
例如在这个问题中,接受的答案说:
通常的经验法则(直接来自 Intel 和 AMD 的优化手册)是每种数据类型都应该按照自己的大小对齐。 int32 应该在 32 位边界上对齐,int64 在 64 位边界上对齐,依此类推。 char 适合任何地方。
因此,通过该答案,地址 alignment 应该可以被sizeof(T)
整除。
关于这个问题的第二个答案 state 认为:
CPU 总是以其字长读取(在 32 位处理器上为 4 个字节),因此当您在支持它的处理器上执行未对齐的地址访问时,处理器将读取多个字。
因此,通过该答案,地址 alignment 应该可以被 CPU 字长整除。
所以我看到一些关于如何优化数据 alignment 以进行 CPU 读/写的相互矛盾的陈述,我不确定我是否理解不正确或者有一些错误的答案? 也许有人可以帮我弄清楚地址 alignment 应该被什么整除。
作为一般的经验法则(也就是说,除非您有充分的理由不这样做),否则您希望将给定 C++ 类型的元素与其 alignment 对齐,即alignof(T)
。 如果该类型想要与 32 位边界对齐(如在最常见的 c++ 实现中实现的int
),它将显示一个合适的(4 字节)alignment。
当然,在T
类型的两个不同对象的基地址之间必须至少有sizeof(T)
字节的空间,这通常是其 alignment 的整数值倍数(实际上很难通过 over-将类型对齐到模板 function,因为它将去除任何外部alignas
属性)。
因此,在大多数用例中,您可以通过执行以下操作:在底层存储中找到与alignof(T)
对齐的第一个基地址,然后以sizeof(T)
的步长从那里向前找到 go 。
这样,您将依靠分配器的用户告诉您他们想要什么。 这正是您想要的,因为优化器可能依赖于有关 alignment 的知识,例如,为双精度浮点数的 arrays 发出 SSE 对齐负载,如果它们对齐错误,这将导致您的程序崩溃。
这导致了以下可能的情况:
int
with sizeof(int) = 4
和alignof(int) = 4
):sizeof(T) = 4 and alignof(T) = 4
0 1 2 3 4 5 6 7 8 9 A B C D E F
[aaaaaaaaaa][bbbbbbbbbb][cccccccccc][dddddddddd]
using T = int[2]
)sizeof(T) = 8 and alignof(T) = 4
0 1 2 3 4 5 6 7 8 9 A B C D E F
[aaaaaaaaaaaaaaaaaaaaaa][bbbbbbbbbbbbbbbbbbbbbb]
using T = alignas(8) char[3]
)。 这里是龙!sizeof(T) = 3 and alignof(T) = 8
0 1 2 3 4 5 6 7 8 9 A B C D E F
[aaaaaaa] [bbbbbbb]
请注意,在过度对齐的示例中有未使用的空间。 这是必要的,因为与 8 字节边界对齐的对象可能不会放在其他任何地方,从而导致潜在的浪费。 此类类型最常见的用途是特定于 CPU 的优化,例如防止虚假共享。
using T = alignas(4) char[5];
)。 这基本上只是对前面过度对齐类型示例的一个小扩展:sizeof(T) = 5 and alignof(T) = 4
0 1 2 3 4 5 6 7 8 9 A B C D E F
[aaaaaaaaaaaaa] [bbbbbbbbbbbbb]
虽然 alignment 可以将第二个 object 放置在基地址4
处,但那里已经存在 object。
将所有这些示例放在一起,需要位于T
类型的两个对象的基地址之间的字节数为:
inline auto object_distance = sizeof(T) % alignof(T) == 0 ? sizeof(T) : sizeof(T) + (alignof(T) - sizeof(T) % alignof(T));
在我分配一块 memory 之后,我想知道块中每个 object 之间需要多少填充以优化 CPU 读/写。
对象之间精确的零填充; 您不允许添加填充。 在 C++ 标准库分配器 model 中,您的allocator<T>::allocate(count)
方法需要分配足够的空间来存储T
类型的count
对象数组。 C++中的Arrays包装紧密; 从数组中的一个T
到另一个T
的偏移量必须是sizeof(T)
。
因此,您不能在分配的存储中的对象之间插入填充。 您可以在您分配的 memory 块的开头插入填充,这样您就可以准确地使用alignof(T)
(您的allocator<T>::allocate
也需要遵守)。 但是返回的指针必须是指向T
对齐存储的指针。 因此,如果您在分配的前面有填充,则需要在调用deallocate
时撤消填充,因为它只获取对齐的存储地址。
当涉及到包含基本类型的结构的 alignment 时,您依赖编译器对这些结构施加其 alignment 要求。 所以对于这个定义:
struct U
{
std::int32_t i;
std::int64_t j;
};
如果编译器认为int64_t
在 8 字节对齐上会更优化,那么编译器将在U
中的i
和j
之间插入适当的填充。 sizeof(U)
将为 16,而alignof(U)
将为 8。
创建 alignment 不是您的工作,并且不允许您为编译器执行此操作。 您必须简单地尊重您在allocator<T>::allocate
调用中给出的任何类型的 alignment。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.