现代x86实现可以从多个先前的商店中存储转发吗?

Bee*_*ope 9 optimization performance x86 assembly micro-optimization

如果负载与两个早期存储重叠(并且负载未完全包含在最早的存储中),现代Intel或AMD x86实现是否可以从两个存储转发以满足负载?

例如,请考虑以下顺序:

mov [rdx + 0], eax
mov [rdx + 2], eax
mov ax, [rdx + 1]
Run Code Online (Sandbox Code Playgroud)

最后的2字节加载从前一个存储区获取其第二个字节,但是它之前的存储区的第一个字节.这个负载可以存储转发,还是需要等到两个先前的存储都提交给L1?

请注意,通过存储转发,我包括任何可以满足仍然存储在缓冲区中的存储的读取的机制,而不是等待它们提交到L1,即使它是一个比最好的情况"转发"更慢的路径.单店"案例.

Iwi*_*ist 13

没有.

至少,不是Haswell,Broadwell或Skylake处理器.在其他英特尔处理器上,限制要么类似(Sandy Bridge,Ivy Bridge),要么更严格(Nehalem,Westmere,Pentium Pro/II/II/4).在AMD上,类似的限制适用.

来自Agner Fog的出色优化手册:

Haswell的/ Broadwell微架构

Intel和AMD CPU的微体系结构

§10.12商店转发摊位

在某些条件下,处理器可以将存储器写入转发到来自相同地址的后续读取.存储转发在以下情况下有效:

  • 当写入64位或更少时,读取相同大小和相同地址后,无论对齐如何.
  • 当写入128或256位之后读取相同大小和相同地址时,完全对齐.
  • 当写入64位或更少的位后,读取一个较小的大小,完全包含在写地址范围内,而不管对齐情况如何.
  • 当任意大小的对齐写入之后是两个半读取,或者四个四分之四的读取等,其在写入地址范围内具有自然对齐.
  • 当对齐的128位或256位写入之后是64位或更少的读取,不会越过8字节边界.

如果内存块跨越64字节高速缓存行边界,则会发生2个时钟的延迟.如果所有数据都具有自然对齐,则可以避免这种情况.

在以下情况下,存储转发失败:

  • 当写入任何大小后,读取更大的大小
  • 当任何大小的写入之后是部分重叠的读取
  • 当写入128位之后是较小的读取,跨越两个64位半部分之间的边界
  • 当写入256位之后是128位读取时,跨越两个128位半部分之间的边界
  • 当写入256位之后是64位或更少的读取跨越四个64位四分之一之间的任何边界

失败的存储转发比成功的存储转发多10个时钟周期.在写入128或256位(未对齐至少16位)之后,惩罚要高得多 - 大约50个时钟周期.

强调补充说

SKYLAKE微架构

Intel和AMD CPU的微体系结构

§11.12商店转发摊位

在某些条件下,Skylake处理器可以将存储器写入转发到来自同一地址的后续读取.存储转发比以前的处理器快一个时钟周期.从同一地址读取的存储器写入需要4个时钟周期,最佳情况是32或64位操作数,其他操作数大小需要5个时钟周期.

当128或256位的操作数未对齐时,存储转发会额外损失最多3个时钟周期.

当任何大小的操作数越过高速缓存行边界时,存储转发通常需要4到5个时钟周期,即可被64字节整除的地址.

写入后跟同一地址的较小读取几乎没有或没有惩罚.

写入64位或更少后跟较小的读取,当读取偏移但完全包含在写入覆盖的地址范围内时,会产生1-3个时钟的损失.

对128或256位的对齐写入,然后读取两个半部分或四个四分之一等中的一个或两个,几乎没有或没有惩罚.不适合半部或四分之一部分读取可能需要额外的11个时钟周期.

读取大于写入或读取覆盖写入和未写入的字节的读取大约需要11个时钟周期.

强调补充说

一般来说:

Agner Fog的文档指出,微架构的一个共同点是,如果写入对齐并且读取是写入值的一半四分之一,则更有可能发生存储转发.

一个测试

使用以下紧密循环的测试:

mov [rsp-16], eax
mov [rsp-12], ebx
mov ecx, [rsp-15]
Run Code Online (Sandbox Code Playgroud)

显示ld_blocks.store_forwardPMU计数器确实增加了.此事件记录如下:

ld_blocks.store_forward [此事件计算加载操作获得真正的Block-on-Store阻止代码阻止存储转发的次数.这包括以下情况: - 前面的商店与负载冲突(不完整的重叠)

  • 由于u-arch限制,商店转发是不可能的

  • 锁定RMW操作不会转发

  • store具有无转发位设置(不可缓存/页面拆分/屏蔽存储)

  • 使用全阻塞商店(主要是围栏和端口I/O)

这表明当只读部分重叠最近的早期存储时,存储转发确实失败(即使在考虑更早的存储时它完全被包含).


Pet*_*des 9

有序Atom可以在不停顿的情况下进行这种存储转发.

Agner Fog没有特别针对Atom提及此案例,但与所有其他CPU不同,它可以存储从存储到更宽或不同对齐的负载的1c延迟.Agner发现的唯一例外是缓存线边界,其中Atom非常糟糕(即使不涉及存储转发,CL分割加载或存储也会有16个周期的惩罚).


这个负载可以存储转发,还是需要等到两个先前的存储都提交给L1?

这里有一个术语问题.许多人会将"这个负载可以存储转发"解释为询问是否可以以低延迟发生,就像快速路径存储转发满足所有要求一样,如@ IWill的回答中所列.(所有加载的数据都来自最近的商店以重叠任何负载,并且满足其他相对/绝对对齐规则).

我一开始以为你错过了第三种可能性,即缓慢但仍然(几乎?)固定的延迟转发,而不是等待提交到L1D,例如,使用一种机制来刮擦整个存储缓冲区(并且可能从L1D加载) Agner Fog和英特尔的优化手册称为"存储转发失败".

但是现在我看到这个措辞是故意的,你真的想问第三个选项是否存在.

您可能想要将其中一些内容编辑到您的问题中.总之,英特尔x86 CPU的三种可能选择是:

  1. 英特尔/ Agner对商店转发成功的定义,其中所有数据仅来自一个具有低和(几乎)固定延迟的最近商店.
  2. 扫描整个存储缓冲区并组装正确字节(根据程序顺序)和(如果必要或始终?)从L1D加载以提供最近未存储的任何字节数据的额外(但有限)延迟.

    这是我们不确定存在的选项.

    它还必须等待来自尚未准备好输入的商店数据uop的所有数据,因为它必须遵守程序顺序.可能会发布一些关于具有未知商店地址的推测性执行的信息(例如,猜测它们不重叠),但我忘了.

  3. 等待所有重叠存储提交到L1D,然后从L1D加载.

    在某些情况下,某些真正的x86 CPU可能会退回到此状态,但它们可能始终使用选项2而不引入StoreLoad屏障.(请记住,x86存储必须按程序顺序提交,并且加载必须按程序顺序进行.这将有效地将存储缓冲区耗尽到这一点,例如mfence,尽管稍后加载到其他地址仍然可以推测性地存储转发或只是获取数据来自L1D.)


中间选项的证据:

Can x86中提出的锁定方案对具有更宽负载的窄存储重新排序,完全包含它?如果存储转发失败需要刷新到L1D,则会起作用.由于它不能在真实硬件上运行mfence,因此有力证据表明真正的x86 CPU正在将来自存储缓冲区的数据与来自L1D的数据合并.因此选项2存在并且在这种情况下使用.

另见Linus Torvalds的解释,x86确实允许这种重新排序,以回应其他提出与SO问题相同的锁定思想的人.

我没有测试存储转发失败/失速惩罚是否可变,但如果没有那么强烈意味着当最佳情况转发不起作用时它会回退到检查整个存储缓冲区.

希望有人会回答x86上存储转载失败的成本是多少?,这正是要求的.如果我能解决它,我会的.

Agner Fog只提到了一个商店转发处罚的号码,并没有说如果缓存未命中商店在未能转发的商店之前飞行,它就会更大.(这会导致很大的延迟,因为由于x86强烈排序的内存模型,商店必须按顺序提交L1D.)他也没有说明数据来自1商店+ L1D与来自的不同情况两个或更多商店的一部分,所以我猜它也适用于这种情况.


我怀疑"失败"的存储转发是很常见的,以至于值得晶体管处理它的速度要快于刷新存储队列并从L1D重新加载.

例如,gcc没有专门尝试避免存储转发停顿,并且它的一些习惯用法导致它们(例如,__m128i v = _mm_set_epi64x(a, b);在32位代码存储/重新加载到堆栈,对于大多数情况,这在大多数CPU上已经是错误的策略,因此,错误报告).这不好,但结果通常不是灾难性的,AFAIK.