为什么有一个比一个词更大的对齐方式



好的,我知道存储与 CPU 字大小的块对齐的数据可以提高访问它的速度。但是这些块通常是 16、32 或 64 位,为什么还有其他连接值,如 128 位或 256 位?我的意思是,无论如何,PC中没有任何处理器使用如此大的寄存器。我认为这与CPU缓存有关吗?我也在辅助存储中看到过这样的对齐方式(但它们实际上要大得多 - 例如 10240 位(。

许多处理器确实具有 128 位 SIMD 寄存器(例如,x86 SSE 寄存器、

ARM Neon 寄存器、MIPS SIMD 架构寄存器(; x86 AVX 将 SSE 寄存器扩展到 256 位,AVX-512 再次将大小翻倍。

但是,还有其他原因需要更大的对齐方式。正如您所猜测的,缓存行为是使用较大对齐方式的一个动机。将较大的数据结构与缓存行的大小对齐(x86 通常为 64 字节,现代系统中通常不小于 32 字节(可确保对任何成员的访问会将相同的其他成员引入缓存。这可用于通过将经常使用的成员(也称为热(或大约同时使用的成员放在同一个缓存块中来减少缓存容量使用和错过率。

例如,考虑使用具有 32 字节缓存块的缓存访问的以下结构:

struct {
int64_t hot1; // frequently used member
int64_t hot2; // frequently used member
int64_t hot3; // frequently used member
int64_t hot4; // frequently used member
// end of 32-byte cache block if 32-byte aligned
int64_t a; // always used by func1, func2
int64_t b; // always used by func2
int64_t c; // always used by func1, func3
int64_t d; // always used by func2, func3
// end of 32-byte cache block if 32-byte aligned
int64_t e; // used by func4
int64_t f; // used by func5
int64_t g; // used by func6
int64_t h; // used by func7
}

如果结构为 32 字节对齐:

  • 访问任何热成员会将所有热成员引入缓存
  • 调用func1func2func3会将abcd带入缓存;如果这些函数在附近及时调用,则数据仍将在缓存中

如果结构是 16 字节对齐但不是 32 字节对齐(16 字节对齐的几率为 50%(:

  • hot1hot2的访问会将 16 字节的不相关数据置于hot1之前,并且不会自动将hot3加载并hot4到缓存中
  • hot3hot4的访问会将ab引入缓存(可能是不必要的(
  • func1func2 的调用更有可能遇到 ab的缓存命中,因为这些缓存与 hot3hot4 位于同一缓存块中,但cd有遗漏,并且不太有用地将ef引入缓存。
  • 调用 func3 将不太有用地将ef带入缓存,但不会ab

即使对于小型结构,对齐也可以防止结构(或仅热或访问时间附近的部分(跨越缓存块边界。 例如,将 24 字节结构与 16 字节的热数据对齐到 16 字节可以保证热数据始终位于同一缓存块中。

缓存块

对齐还可用于保证两个锁(或由不同线程访问并由至少一个线程写入的其他数据元素(不共享同一个缓存块。这样可以避免错误的共享问题。(错误共享是指不同线程使用的不相关数据共享缓存块。一个线程的写入将从所有其他缓存中删除该缓存块。如果另一个线程写入该块中不相关的数据,它将从第一个线程的缓存中删除该块。对于使用链接加载/存储条件设置锁的 ISA,这可能会导致存储条件失败,即使没有实际的数据冲突也是如此。

类似的对齐注意事项适用于虚拟内存页大小(通常为 4KiB(。通过保证附近及时访问的数据在较少的页数中,存储虚拟内存地址转换的缓存(转换后备缓冲区 [TLB](将不会有那么大的容量压力。

还可以在对象缓存中使用对齐来减少缓存冲突未命中,当项目具有相同的缓存索引时会发生缓存冲突未命中。(缓存通常仅通过选择一些最低有效位来编制索引。在每个索引中,可以使用有限数量的块,称为集合。如果想要共享索引的块数多于集合中的块数(关联性或方式数(,则必须从缓存中删除集合中的一个块以腾出空间。一个 2048 字节、完全对齐的内存块可以容纳上述结构的 21 个副本,并带有 32 字节的填充块(可用于其他目的(。这保证了来自不同块的热成员只有 33.3% 的机会使用相同的缓存索引。(在块中分配,即使未对齐,也保证块中的 21 个副本中的任何一个都不会共享缓存索引。

在缓冲区中,大对齐方式也很方便,因为简单的按位and可以生成缓冲区的起始地址或缓冲区中的字节数。

还可以利用对齐来提供指针压缩(例如,64 字节对齐将允许 32 位指针寻址 256 GiB 而不是 4 GiB,但加载指针时以 6 位左移为代价(。同样,指向对齐对象的指针的最低有效位可用于存储元数据,这需要使用指针之前将位清零and

以下是我使用的对齐方式:

SSE:           16 bytes
AVX:           32 bytes
cache-line:    64 bytes
page:        4096 bytes

SSE 和 AVX 都提供加载和存储指令,SSE 需要对齐到 16 字节或 AVX 需要对齐 32 字节。 例如

SSE: _mm_load_ps() and _mm_store_ps()
AVX: _mm256_load_ps() and _mm256_store_ps()

但是,它们还提供不需要对齐的说明:

SSE: _mm_loadu_ps() and _mm_storeu_ps()  
AVX: _mm256_loadu_ps() and _mm256_storeu_ps()

在 Nahellem 之前,未对齐的加载/存储即使在对齐的内存上也比需要对齐的指令具有更大的延迟/吞吐量。 但是,由于Nahelem,它们在对齐的内存上具有相同的延迟/吞吐量,这意味着没有理由再使用需要对齐的加载/存储指令。 这并不意味着对齐的内存不再重要。

如果 16 或 32 个字节

穿过高速缓存行,并且这 16 或 32 个字节加载到 SSE/AVX 寄存器中,则可能会导致停顿,因此它也有助于与高速缓存行对齐。 在实践中,我通常对齐 64 字节。

在具有多个处理器在处理器之间共享内存的多插槽系统上,访问每个处理器的主内存

比访问每个处理器的主内存慢。 出于这个原因,它可以帮助确保内存不会在虚拟页面之间拆分,虚拟页面通常(但不一定是(4096 字节。

最新更新