所以我读了几篇关于内存对齐的文章(以及 StackOverflow 上的一些问题),我理解了为什么这样的结构:
struct A
{
char c;
int i;
}
会有填充物。 此外,很明显,如果处理器只能从对齐的偏移量中读取,则从未对齐的内存中获取数据会更慢。
但是为什么处理器只能从对齐的内存中读取?为什么它不能从随机地址读取数据?你知道,从随机存取存储器......
我取决于处理器。有些处理器根本不允许未对齐的访问。其他处理器可以,但速度往往较慢。
在您的示例中(如果字段被打包并且允许未对齐访问,如果处理器尝试读取 i,通常需要从内存中获取它。
某些处理器会因多次访问而检索未对齐的数据而影响性能。其他人根本不允许。
处理器针对最常见的用例进行了优化:数据正确对齐时。这就是为什么对齐读取是首选的原因。
但这并不能回答你的问题。是什么让对齐读取更有效?
答案是,现代处理器不喜欢从内存中读取数据。他们从缓存中读取数据,速度要快几个数量级。
那些使用“缓存线”组织的缓存。典型的缓存行为 64 字节,并且始终与其大小对齐,您可以在here读取精确值。当您读取对齐值时,可以保证大小小于或等于缓存行的值将位于单个缓存行中。对于未对齐的值,可能部分值在缓存中,而其他部分只能从 RAM 访问。 更新:更详细的解释。
movq rax, qword ptr [rdi]
它正在从指针
rdi
读取64位数字到寄存器
rax
。如果 rdi
正确对齐(意味着它可以被 8 整除),则保证它位于单个缓存行中。查看所有 8 个可能位置的可视化图表:
CACHE LINE
+--------------------------------------------------------------------------+
| 0 <- offset 8 16 24 |
| +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| +-+-+-+-+-+-+-+-- +-+-+-+-+-+-+-+-- +-+-+-+-+-+-+-+-- +-+-+-+-+-+-+-+-- |
| |
| 32 36 44 52 |
| +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| +-+-+-+-+-+-+-+-- +-+-+-+-+-+-+-+-- +-+-+-+-+-+-+-+-- +-+-+-+-+-+-+-+-- |
| |
+--------------------------------------------------------------------------+
因此,如果发生缓存未命中,它将仅限于单个缓存,因为当获取缓存时,它会立即获取我们值的整个 8 个字节。
如果值错位,我们可能会遇到这样的情况:
+--------------------+------------------+
|Cache line 1 | Cache line 2 |
| +-+-+-+---+-+-+-+ |
| | | | | | | | | | |
| +-+-+-+---+-+-+-+ |
| | |
+--------------------+------------------+
如果我们的值跨越缓存行之间的边界,CPU 将需要从内存加载两倍的数据(2 个完整的缓存行)。
同样的情况也适用于页面错误,但我预计,特定读取导致 2 个 TLB 丢失的机会相当罕见。
此外,当您对未对齐的值进行内存
写入时,情况可能会更糟,因为存在CPU缓存失效协议,并且此类写入将使两个缓存线无效。如果经常从多个线程访问这样的缓存行,情况会更糟。事实上,许多使用原子整数的高效同步机制通常将它们与 64 位对齐,以避免不必要的缓存失效。 例如,参见横梁
CachePlated