在 64 位系统中将小结构分配给 32 位对齐

Allocate small struct to 32 bit aligned in 64 bit system

问题:我正在实现一个非阻塞数据结构,其中线程使用 CAS 操作更改共享指针。由于指针可以被回收,我们有 ABA 问题。为避免这种情况,我想为每个指针附加一个版本。这称为 版本指针 。 CAS128 被认为比 CAS64 更贵,所以我尽量避免超过 64 位。

我正在尝试实现版本化指针。在 32b 系统中,版本指针是一个 64b 结构,其中高 32 位是指针,低 32 位是它的版本。这使我可以使用 CAS64 自动更改指针。

我在使用 64b 系统时遇到问题。在这种情况下,我仍然想使用 CAS64 而不是 CAS128,因此我试图分配一个与 4GB 对齐的指针(即 32 个零)。然后我可以使用掩码来推断指针和版本。

我尝试使用 alligned_malloc、填充和 std::align 的解决方案,但这些涉及分配非常大量的内存,例如,alligned_malloc(1LL << 32, (1LL << 32)* sizeof(void*)) 分配 4GB 内存。另一种解决方案是使用内存映射文件,但这涉及我们试图避免的同步。

有没有办法分配 8B 的内存,与我缺少的 4GB 对齐?

首先,一种不可移植的解决方案,它将代码复杂性限制在分配点(请参阅下文了解另一种使使用点更复杂但应该是可移植的方法);它仅适用于 POSIX 系统(不适用于 Windows),但您可以将开销减少到页面大小(不是 8 字节,但在 64 位系统的上下文中,浪费 4088 字节是 如果你不经常这样做,那就太糟糕了;显然,你的问题的性质意味着你不可能浪费超过每 4 GB 的 sysconf(_SC_PAGESIZE) - 8 字节, 所以这不是 坏) 通过以下机制:

  1. mmap 4 GB 匿名内存(非文件支持;传递 -1fd 并包含 MAP_ANONYMOUS 标志)
  2. 计算该块内 4 GB 对齐指针的地址
  3. munmap 该地址之前的内存,以及该地址之后 sysconf(_SC_PAGE_SIZE) 字节的内存

之所以可行,是因为内存映射不是单一的;它们可以零碎地取消映射,可以无错误地重新映射各个页面,等等。

请注意,如果您缺少交换 space,4 GB 的简短请求可能会导致问题(例如,在 Linux 禁用了启发式过度使用的系统上,它可能无法分配内存,如果它不能用交换支持它,即使你从不使用它的大部分)。您可以尝试将 MAP_NORESERVE 传递给原始请求,然后执行取消映射,然后使用 MAP_FIXED(没有 MAP_NORESERVE)重新映射该单个页面,以确保可以在不触发 MAP_NORESERVE 的情况下使用分配=23=] 在撰写本文时。


如果你不能用POSIXmmap,如果真的不能用CAS128,你可能要考虑给这些指针一个segmented memory model like the old x86 scheme。您预先块分配 4 GB 段(它们不需要任何特殊对齐),并让您的“指针”从段的基地址开始为 32 位 offsets;您不能使用整个 64 位地址 space(除非您允许多个选择器,例如可能通过重新利用版本号字段的一部分;您可能可以使用几百万个版本而不是四十亿个版本全部),但是如果您不需要这样做,这可以让您拥有一个在分配后永远不会改变的基地址(因此不需要原子),其偏移量适合您所需的 32 位字段。因此,不要通过以下方式获取数据:

data = *mystruct.pointer;

你有一个这样的段指针提前初始化:

char *const base_address = new char[1ULL << 32];  // Or use smart pointer of your choosing

将其包装在子分配器中以对 space 进行分区,现在查找改为:

data = *reinterpret_cast<REAL_TYPE_HERE*>(&base_address[mystruct.pointer]);

我敢肯定有一些漂亮的方法可以用自定义分配器、自定义 operator news 更好地包装它,你有什么,但我从来没有在 C++ 中这样做过(我已经完成了C 中的类似魔术,其中没有使其“漂亮”的工具),我可能会弄错,所以我将把它留作练习。