Pro*_*ear 2 c cpu cpu-architecture memory-alignment low-level
我正在尝试重新实现 malloc 并且我需要了解对齐的目的。据我了解,如果内存对齐,代码将执行得更快,因为处理器不必采取额外的步骤来恢复被切割的内存位。我想我明白 64 位处理器读取 64 位 x 64 位内存。现在,让我们假设我有一个按顺序排列的结构(没有填充):一个字符、一个短字符、一个字符和一个整数。为什么短路会错位?我们拥有区块中的所有数据!为什么它必须在一个 2 的倍数的地址上。对于整数和其他类型,同样的问题?
我还有第二个问题:使用我之前提到的结构,处理器如何知道当它读取它的 64 位时前 8 位对应于一个字符,然后接下来的 16 位对应于一个短等等......?
影响甚至可以包括正确性,而不仅仅是性能:C 未定义行为 (UB) 如果您的short对象不满足alignof(short). (默认情况下加载/存储指令需要对齐的 ISA 会出现故障,例如 SPARC 和 MIPS64r6 之前的 MIPS)
或者如果_Atomic int没有alignof(_Atomic int).
(alignof(T) = sizeof(T)在任何给定的 ABI 中,通常最大为某个大小,通常注册宽度或更宽)。
malloc应该返回内存,alignof(max_align_t)因为您没有关于如何使用分配的任何类型信息。
对于小于 的分配sizeof(max_align_t),如果需要,您可以返回仅自然对齐的内存(例如,4 字节分配按 4 字节对齐),因为您知道存储不能用于具有更高对齐要求的任何内容。
过度对齐的东西,比如动态分配的等价物,alignas (16) int32_t foo需要使用像 C11 这样的特殊分配器aligned_alloc。如果您正在实现自己的分配器库,您可能希望支持aligned_realloc 和aligned_calloc,以填补ISO C 无缘无故留下的空白。
如果分配大小不是对齐的倍数,请确保您没有实现 ISO C++17 要求aligned_alloc失败。没有人想要一个分配器拒绝从 16 字节边界开始分配 101 个浮点数,或者更大的分配器以获得更好的透明大页面。 aligned_alloc 函数要求以及如何解决 AVX 加载/存储操作的 32 字节对齐问题?
我想我明白 64 位处理器读取 64 位 x 64 位内存
不。数据总线宽度和突发大小,以及加载/存储执行单元的最大宽度或实际使用的宽度,不必与整数寄存器的宽度相同,或者 CPU 定义其位数。(而在现代高性能 CPU 中通常不是。例如,32 位 P5 Pentium 具有 64 位总线;现代 32 位 ARM 具有执行 64 位原子访问的加载/存储对指令。)
处理器从 DRAM / L3 / L2 缓存中读取整个缓存行到 L1d 缓存中;现代 x86 上的 64 字节;在其他一些系统上为 32 字节。
当读取单个对象或数组元素时,它们从 L1d 缓存中读取元素宽度。例如,uint16_t对于 2 字节加载/存储,数组可能仅受益于与 2 字节边界对齐。
或者,如果编译器使用 SIMD 对循环进行矢量化,则一次uint16_t可以读取 16 或 32个字节的数组,即 8 或 16 个元素的 SIMD 向量。(或什至 64 与 AVX512)。将数组与预期的向量宽度对齐可能会有所帮助;当它们不跨越缓存线边界时,未对齐的 SIMD 加载/存储在现代 x86 上运行得很快。
缓存行拆分,尤其是页面拆分是现代 x86 因未对齐而变慢的地方;在缓存线中未对齐通常不是因为它们将晶体管用于快速未对齐加载/存储。其他一些 ISA 会因任何未对齐而变慢,甚至出现故障,即使在高速缓存行内也是如此。解决方案是一样的:给类型自然对齐:alignof(T) = sizeof(T)。
在您的 struct 示例中,即使short未对齐,现代 x86 CPU 也不会受到影响。 alignof(int) = 4在任何普通的 ABI 中,整个结构都有alignof(struct) = 4,所以char;short;char块从 4 字节边界开始。因此short包含在单个 4 字节双字中,不会跨越任何更宽的边界。AMD 和英特尔都以最高效率处理这个问题。(并且 x86 ISA 保证在与 P5 Pentium 或更高版本兼容的 CPU 上对它的访问是原子的,甚至是未缓存的:为什么在 x86 上自然对齐的变量上的整数赋值是原子的?)
一些非 x86 CPU 会因未对齐的短而受到惩罚,或者必须使用其他指令。(因为您知道相对于对齐的 32 位块的对齐方式,对于加载,您可能会执行 32 位加载和移位。)
所以是的,访问包含 的单个单词没有问题short,但问题在于加载端口硬件将其提取并零扩展(或符号扩展)short到一个完整的寄存器中。 这就是 x86 花费晶体管来加快速度的地方。(@Eric对此问题先前版本的回答更详细地介绍了所需的转换。)
将未对齐的存储提交回缓存也很重要。例如,L1d 缓存可能在 32 位或 64 位块(我将其称为“缓存字”)中具有 ECC(针对位翻转的纠错)。因此,仅写入缓存字的一部分是一个问题,因为这个原因以及将其移动到您要访问的缓存字内的任意字节边界也是一个问题。(在以这种方式处理窄存储的缓存中,存储缓冲区中相邻窄存储的合并可以产生全宽提交,避免 RMW 循环更新字的一部分)。请注意,我现在说“单词”是因为我在谈论更面向单词的硬件,而不是像现代 x86 那样围绕未对齐的加载/存储设计。 看是否有任何现代 CPU 的缓存字节存储实际上比字存储慢?(存储单个字节仅比 unaligned 稍微简单short)
(如果short跨越两个缓存字,当然需要分开 RMW 周期,每个字节一个。)
当然short,由于简单的原因而未对齐,alignof(short) = 2并且它违反了此 ABI 规则(假设 ABI 确实具有该规则)。因此,如果您将指向它的指针传递给某个其他函数,则可能会遇到麻烦。尤其是在负载未对齐的 CPU 上,而不是硬件处理在运行时未对齐的情况。然后你可以得到像为什么未对齐访问 mmap'ed 内存有时在 AMD64 上出现段错误这样的情况?其中 GCC 自动矢量化预计通过执行一些 2 字节元素标量的倍数来达到 16 字节边界,因此违反 ABI 会导致 x86 上的段错误(通常可以容忍错位。)
有关内存访问的完整详细信息,从 DRAM RAS / CAS 延迟到缓存带宽和对齐,请参阅每个程序员应该了解的关于内存的内容?它几乎仍然相关/适用
另外内存对齐的目的有一个很好的答案。SO 的memory-alignment标签中有很多其他好的答案。
有关(有点)现代英特尔加载/存储执行单元的更详细信息,请参阅:https : //electronics.stackexchange.com/questions/329789/how-can-cache-be-that-fast/329955#329955
处理器如何知道当它读取它的 64 位时前 8 位对应于一个字符,然后接下来的 16 位对应于一个短等等......?
它没有,除了它正在运行以这种方式处理数据的指令之外。
在汇编/机器代码中,一切都只是字节。 每条指令都准确地指定了如何处理哪些数据。由编译器(或人类程序员)在原始字节数组(主内存)之上实现具有类型的变量和 C 程序的逻辑。
我的意思是,在 asm 中,您可以运行任何您想要的加载或存储指令,并且取决于您在正确的地址上使用正确的指令。您可以将重叠两个相邻int变量的4 个字节加载到浮点寄存器中,然后addss在其上运行(单精度 FP 添加),CPU 不会抱怨。但您可能不想这样做,因为让 CPU 将这 4 个字节解释为 IEEE754 binary32 浮点数不太可能有意义。
| 归档时间: |
|
| 查看次数: |
743 次 |
| 最近记录: |