Performance 什么';在x86上成功的未对齐访问的实际效果是什么?

Performance 什么';在x86上成功的未对齐访问的实际效果是什么?,performance,memory,x86,alignment,memory-alignment,Performance,Memory,X86,Alignment,Memory Alignment,我经常听说,未对齐的访问是不好的,因为它们要么会导致运行时错误并使程序崩溃,要么会减慢内存访问速度。然而,我找不到任何实际的数据来说明他们会把事情拖慢多少 假设我在x86上,有一些(但未知)未对齐的访问-实际可能出现的最严重的减速是什么?在不消除所有未对齐的访问并比较两个版本代码的运行时的情况下,我如何估计它?对于大多数x86 SSE加载/存储指令,这取决于指令(不包括未对齐的变体),它将导致错误,这意味着它可能会使程序崩溃或导致异常处理程序的大量往返(这意味着几乎或所有性能都会丢失)。未对齐的

我经常听说,未对齐的访问是不好的,因为它们要么会导致运行时错误并使程序崩溃,要么会减慢内存访问速度。然而,我找不到任何实际的数据来说明他们会把事情拖慢多少


假设我在x86上,有一些(但未知)未对齐的访问-实际可能出现的最严重的减速是什么?在不消除所有未对齐的访问并比较两个版本代码的运行时的情况下,我如何估计它?

对于大多数x86 SSE加载/存储指令,这取决于指令(不包括未对齐的变体),它将导致错误,这意味着它可能会使程序崩溃或导致异常处理程序的大量往返(这意味着几乎或所有性能都会丢失)。未对齐的加载/存储变体以两倍于IIRC的周期量运行,因为它们执行部分读/写操作,因此执行该操作需要2个周期(除非您运气好,且其在缓存中,这大大降低了惩罚)

对于一般的x86加载/存储指令,代价是速度,因为读或写需要更多的周期。未对齐也可能会影响缓存,导致缓存线分裂和缓存边界跨越。它还可以防止读和写的原子性(这对于x86的所有对齐读/写都是有保证的,屏障和传播是另外一回事,但是对未对齐的数据使用锁定指令可能会导致异常或大大增加bu锁所带来的已经很严重的惩罚),这对于并发编程来说是不允许的

详细介绍了上述每个问题,它们的副作用以及如何解决它们


在原始循环吞吐量方面应该有您想要的准确数字。

一般来说,在现代处理器上估计速度是极其复杂的。这不仅适用于未对齐的访问,而且也适用于一般情况

现代处理器具有流水线结构,指令的无序和可能并行执行以及许多其他可能影响执行的事情

如果不支持未对齐的访问,则会出现异常。但如果支持,则可能会或可能不会出现减速,这取决于许多因素。这些因素包括在未对齐的访问之前和之后执行的其他指令(因为处理器可以在执行之前的指令时开始提取数据,或者在等待时继续执行后续指令)

如果未对齐的访问跨缓存线边界进行,则会发生另一个非常重要的区别。一般来说,未对齐的访问可能会发生2倍的缓存访问,真正的减速是如果访问跨缓存线边界并导致双缓存未命中。在最坏的情况下,2字节未对齐的读取可能需要cessor将两条缓存线刷新到内存中,然后从内存中读取两条缓存线。这意味着大量数据正在移动


优化的一般规则也适用于此:首先编码,然后测量,然后当且仅当出现问题时,找出解决方案。

在某些英特尔微体系结构上,由缓存线边界拆分的负载比通常情况下需要更长的十几个周期,而由页面边界拆分的负载则需要更长的200个周期如果负载在一个循环中始终未对齐,那么即使
palignr
不是一个选项,也值得手动执行两个对齐的负载并合并结果。即使SSE的未对齐负载也不会保存您,除非它们完全从中间拆分


在AMD上,这从来都不是一个问题,Nehalem中的问题基本上消失了,但仍然有很多Core2。

经验法则:在大多数架构上,未对齐的读取与对齐的读取相比会导致约2倍的性能损失,因为获取数据和修复数据需要两个读取周期。写入稍微复杂一些。related:有一些关于现代Intel上缓存线拆分和页面拆分对吞吐量和延迟影响的具体细节。查阅了Agner Fog的论文,但找不到具体的数字。你能给我指出正确的页面/表格吗?@NitsanWakart:未对齐的SSE指令列在这里:,对正常指令的惩罚如果您需要查阅开发人员手册中相应的英特尔章节(第8章或第9章IIRC,至少未对齐的读取需要两倍的周期),我特别关注使用最新(post Core2)的未对齐(而不是跨缓存线)访问MOV的惩罚CPU。在Agner的指令表cost中,我找不到惩罚,除了对齐数据的一般建议外,我在英特尔手册中找不到相关参考。@NitsanWakart:4.1.1《英特尔体系结构和指令集手册》指出,任何未对齐的访问都需要2个加载/存储,这基本上会产生双倍的周期(但这可能根据其他条件而有所不同):
跨越4字节边界的字或双字操作数或跨越8字节边界的四字操作数被视为未对齐,需要两个单独的内存总线周期进行访问。