自定义分配器和内存对齐

问题描述 投票:0回答:2

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

我目前正在尝试实现线性分配器,并且在内存对齐方面遇到了困难。分配了一块内存后,我想知道在该块中的每个对象之间需要多少填充以优化cpu读/写。我不确定地址对齐是否应整除

  • 根据cpu字的大小(在32位计算机上为4字节,在64位计算机上为8字节)
  • sizeof(T)
  • alignof(T)

我在不同的地方阅读了不同的答案。例如,在this问题中,可接受的答案是:

通常的经验法则(直接来自Intel和AMD的优化手册)是,每种数据类型都应按其自己的大小对齐。一个int32应该在32位边界上对齐,int64应该在64位边界上对齐边界等。一个char将适合任何地方。

因此,根据该答案,似乎地址对齐方式应被sizeof(T)整除。

关于this问题的第二个回答状态:

CPU始终以其字长读取(32位处理器上为4字节),因此,当您执行未对齐的地址访问时,支持它—处理器将读取多个单词。

因此,根据该答案,看起来地址对齐应该被cpu字大小所除。

因此,我看到一些有关如何优化CPU读/写的数据对齐的矛盾声明,并且不确定是否理解不正确或答案有误?也许有人可以为我清除有关地址对齐方式可以被整除的信息。

c++ c++11 memory-management cpu allocator
2个回答
2
投票

作为一般的经验法则(也就是说,除非您有充分的理由,否则请执行此操作),您希望将给定C ++类型的元素与其对齐方式对齐,即alignof(T)。如果类型想要与32位边界对齐(如在大多数常见的c ++实现中实现了int),则它将显示合适的(4字节)对齐。

当然,在两个T类型的不同对象的基址之间,必须至少有sizeof(T)个字节的空间,该空间通常是其对齐方式的整数倍(实际上很难通过模板函数的过度对齐类型,因为它将去除任何外部alignas属性)。

因此,在大多数用例中,执行以下操作将可以:在您的基础存储中找到与alignof(T)对齐的第一个基地址,然后以sizeof(T)的步骤从那里继续前进。

这样,您将依靠分配器的用户来告诉您他们想要什么。这正是您想要的,因为优化器可能依赖于对齐的知识,例如,为双精度浮点数组发出SSE对齐的负载,如果对齐不正确,将导致程序崩溃。

沿着兔子洞走

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

  1. 简单类型,具有单词长度和单词对齐方式(例如,intsizeof(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. 大小为对齐方式倍数的类型(例如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. 过度对齐的类型,其对齐方式大于大小(例如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

  1. 最后,在某些情况下对象尺寸稍大,但不是其对齐方式的整数倍(例如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));

1
投票

分配内存块后,我想知道在该块中的每个对象之间需要多少填充以优化cpu读/写。

对象之间的填充精确为零;您不允许添加填充。在C ++标准库分配器模型中,需要您的allocator<T>::allocate(count)方法分配足够的空间来存储类型为countT对象的数组。 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中的jU之间插入适当的填充。 sizeof(U)将为16,alignof(U)将为8。

创建对齐方式不是您的工作,并且不允许这样做。您只需尊重allocator<T>::allocate调用中给出的任何类型的对齐方式。

© www.soinside.com 2019 - 2024. All rights reserved.