我正在尝试根据此处的要求实现自定义分配器以与std容器一起使用:https://en.cppreference.com/w/cpp/named_req/Allocator
我目前正在尝试实现线性分配器,并且在内存对齐方面遇到了困难。分配了一块内存后,我想知道在该块中的每个对象之间需要多少填充以优化cpu读/写。我不确定地址对齐是否应整除
sizeof(T)
alignof(T)
我在不同的地方阅读了不同的答案。例如,在this问题中,可接受的答案是:
通常的经验法则(直接来自Intel和AMD的优化手册)是,每种数据类型都应按其自己的大小对齐。一个int32应该在32位边界上对齐,int64应该在64位边界上对齐边界等。一个char将适合任何地方。
因此,根据该答案,似乎地址对齐方式应被sizeof(T)
整除。
关于this问题的第二个回答状态:
CPU始终以其字长读取(32位处理器上为4字节),因此,当您执行未对齐的地址访问时,支持它—处理器将读取多个单词。
因此,根据该答案,看起来地址对齐应该被cpu字大小所除。
因此,我看到一些有关如何优化CPU读/写的数据对齐的矛盾声明,并且不确定是否理解不正确或答案有误?也许有人可以为我清除有关地址对齐方式可以被整除的信息。
作为一般的经验法则(也就是说,除非您有充分的理由,否则请执行此操作),您希望将给定C ++类型的元素与其对齐方式对齐,即alignof(T)
。如果类型想要与32位边界对齐(如在大多数常见的c ++实现中实现了int
),则它将显示合适的(4字节)对齐。
当然,在两个T
类型的不同对象的基址之间,必须至少有sizeof(T)
个字节的空间,该空间通常是其对齐方式的整数倍(实际上很难通过模板函数的过度对齐类型,因为它将去除任何外部alignas
属性)。
因此,在大多数用例中,执行以下操作将可以:在您的基础存储中找到与alignof(T)
对齐的第一个基地址,然后以sizeof(T)
的步骤从那里继续前进。
这样,您将依靠分配器的用户来告诉您他们想要什么。这正是您想要的,因为优化器可能依赖于对齐的知识,例如,为双精度浮点数组发出SSE对齐的负载,如果对齐不正确,将导致程序崩溃。
这会导致以下可能的情况:
int
和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的优化,例如防止false sharing。
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]
虽然通过对齐可以将第二个对象放置在基地址4
上,但是那里已经有一个对象。
将所有这些示例放在一起,需要在T
类型的两个对象的基址之间的字节数是:
inline auto object_distance = sizeof(T) % alignof(T) == 0 ? sizeof(T) : sizeof(T) + (alignof(T) - sizeof(T) % alignof(T));
分配内存块后,我想知道在该块中的每个对象之间需要多少填充以优化cpu读/写。
对象之间的填充精确为零;您不允许添加填充。在C ++标准库分配器模型中,需要您的allocator<T>::allocate(count)
方法分配足够的空间来存储类型为count
的T
对象的数组。 C ++中的数组紧密包装。从数组中一个T
到另一个T
的偏移量必须为sizeof(T)
。
所以您不能在分配的存储中的对象之间插入填充。您可以在分配的内存块的开头插入填充,以便您可以准确地使用alignof(T)
(也需要注意allocator<T>::allocate
)。但是返回的指针必须是指向T
的对齐存储的指针。因此,如果在分配的开头有填充,则需要一种方法来在调用deallocate
时撤消填充,因为它仅获取对齐的存储地址。
涉及包含基本类型的结构的对齐时,您依赖于编译器将对齐要求强加于这些结构上。因此,对于此定义:
struct U
{
std::int32_t i;
std::int64_t j;
};
如果编译器认为int64_t
的最佳选择是8字节对齐,则编译器将在i
中的j
和U
之间插入适当的填充。 sizeof(U)
将为16,alignof(U)
将为8。
创建对齐方式不是您的工作,并且不允许这样做。您只需尊重在allocator<T>::allocate
调用中给出的任何类型的对齐方式。