【发布时间】:2013-05-09 10:42:58
【问题描述】:
我试图弄清楚为什么需要数据对齐/填充。来自维基百科:
“当现代计算机读取或写入内存地址时,它将以字大小的块执行此操作”
但我可以使用 x86 的 movb 指令以字节分辨率清晰地往返移动数据。我在这里错过了什么?
【问题讨论】:
标签: architecture x86 x86-64
我试图弄清楚为什么需要数据对齐/填充。来自维基百科:
“当现代计算机读取或写入内存地址时,它将以字大小的块执行此操作”
但我可以使用 x86 的 movb 指令以字节分辨率清晰地往返移动数据。我在这里错过了什么?
【问题讨论】:
标签: architecture x86 x86-64
字对齐的内存访问比字节对齐的快得多。这使得传输大块数据的速度更快。您可以寻址单个字节,但可能会从内存中读取一个字并在内部减少为一个字节。这会使访问速度变慢。
【讨论】:
[buf + 0] 复制到[buf + 1] 的asm 循环进行基准测试来证明这一点:字节加载旁边的字节存储。如果加载必须等待存储,则循环将成为存储转发延迟的瓶颈(在 Haswell 上约为 5 个周期),因为副本使其循环承载。但是字节加载独立于同一个字中的字节存储,因此循环仅在每个时钟一次存储上出现瓶颈,而不是每 5 个时钟一次。
这是一个常见的误解。 字节访问不需要对该缓存行的包含 32 位或 64 位块(或用于未缓存访问的内存)进行读取-修改-写入。见Can modern x86 hardware not store a single byte to memory?。
单字节访问自动为naturally aligned。这意味着与访问的宽度对齐,因此它不会跨越任何比自身更宽的边界。
一个字加载或存储仍然是一个单一的事务,除非它跨越缓存线边界(在这种情况下,CPU 内部必须访问两个缓存线的相关部分)。因此,该引用仅适用于机器字大小的访问。 (注意,英特尔术语中的word 是 16 位,而不是现代 x86 CPU 的寄存器或总线宽度。这就是我在上一句中说“机器字”的原因。)
因此向 C 中的结构添加填充不是因为字节访问对于字节大小的字段效率低下,而是为了宽于一个字节的对象自然对齐(例如,int 跟随char 在结构中)。
与字节访问不同,一些相对常见的平台支持或不支持直接非对齐访问,在那些支持的平台上,非对齐访问可能效率较低,尤其是在跨越缓存行时。 C 编译器将结构视为对其最对齐的成员具有对齐要求。例如由于 double 成员,int、char 和 double 的结构将具有 64 位对齐,因此相对于结构对齐 double 的填充也将在绝对意义上对齐它,所以结构成员总是保持自然对齐。
即使在没有未对齐访问惩罚的假设平台上,具有未对齐对象也会使依赖原子读写的memory models 的实现大大复杂化,因为许多平台保证这些操作only if they are aligned 的原子性。
现代 CPU 以高速缓存行大小的块传输数据,而不仅仅是 32 位或 64 位字。除非您正在访问不可缓存的内存区域(例如,设备驱动程序中的内存映射 I/O),在这种情况下,您实际上会通过外部获得字节、16 位、32 位或 64 位访问巴士。
只要不跨越 64 位边界,现代 x86 CPU 上的非对齐访问就不会受到任何惩罚。 (特别是在 Intel 上,除非您越过缓存线边界,否则未对齐的加载/存储不会受到惩罚)。
另请参阅How can I accurately benchmark unaligned access speed on x86_64,以及x86 标签 wiki 中的性能调整链接。
【讨论】:
movdqu 很贵,但即使地址对齐也很贵。如果您知道要添加什么来回答该部分问题,请对此答案进行编辑;我的回答主要是为了纠正对字节加载/存储的误解。