繁体   English   中英

自定义分配器和 memory alignment

[英]Custom allocator and memory alignment

我正在尝试根据此处的要求实现自定义分配器以使用标准容器: https://en.cppreference.com/w/cpp/named_req/Allocator

我目前正在尝试实现线性分配器,但我很难使用 memory alignment。
在我分配一块 memory 之后,我想知道块中每个 object 之间需要多少填充以优化 CPU 读/写。 我不确定地址 alignment 是否应该是整除的

  • 按 cpu 字长(32 位机器上 4 个字节,64 位机器上 8 个字节)
  • 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 对齐负载,如果它们对齐错误,这将导致您的程序崩溃。

走下兔子洞

这导致了以下可能的情况:

  1. 简单类型,具有字长和字 alignment (例如, int with sizeof(int) = 4alignof(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]
  1. 大小是其 alignment 倍数的类型(例如, 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]
  1. 过度对齐的类型,其 alignment 比 size 大(例如, 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 的优化,例如防止虚假共享

  1. 最后,有些对象的大小大于但不是 alignment 的倍数,但不是 integer 的倍数(例如, 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中的ij之间插入适当的填充。 sizeof(U)将为 16,而alignof(U)将为 8。

创建 alignment 不是您的工作,并且不允许您编译器执行此操作。 您必须简单地尊重您在allocator<T>::allocate调用中给出的任何类型的 alignment。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM