现代x86实现能否从多个先前的存储中进行store-forward操作?

12

如果一个负载与两个早期的存储重叠(而且负载不完全包含在最早的存储中),现代英特尔或AMD x86实现可以从两个存储中转发以满足负载吗?

例如,考虑以下序列:

mov [rdx + 0], eax
mov [rdx + 2], eax
mov ax, [rdx + 1]

最后的2字节加载操作从紧随其后的存储器(store)中读取第二个字节,但从再前面的存储器(store)中读取第一个字节。这个加载操作能够被存储器(store)前向传递(store-forwarded),还是需要等待前面的两个存储器(store)都提交到L1缓存之后才能执行?

请注意,在此处“store-forwarding”包括任何可以满足从存储器中读取的数据的机制,即使它比最佳情况下“从单个存储器进行前向传递”的速度要慢。


警告:您使用的16位操作数可能会导致解码时出现长度更改前缀惩罚,如果我没记错的话。 - Iwillnotexist Idonotexist
2
@IwillnotexistIdonotexist:操作数大小前缀仅对具有16位立即数的指令进行长度更改(如果没有前缀,则该立即数将为32位)。 因此,add cx, 12766 opcode modrm imm8)是可以的,add cx, 12866 opcode modrm imm16)不行。 还要注意,最近的英特尔CPU不会在mov-immediate上LCP停顿,只会在其他ALU指令上停顿。(而且,LCP停顿只会影响解码,而不会影响uop缓存)。 - Peter Cordes
@PeterCordes 啊!所以我肯定记错了 :-) 它曾经在Core 2上是一个更大的问题,我仍然有一台Penryn机器。 - Iwillnotexist Idonotexist
就我个人而言,我选择了16字节的加载,这样它就完全包含在两个先前的存储器中,而32位的加载可能会引入另一个复杂性(也许不会?)因为它不完全包含在任何一个加载器中(但它包含在它们的组合中)。 - BeeOnRope
2个回答

20

编号

至少在Haswell、Broadwell或Skylake处理器上不行。在其他英特尔处理器上,限制要么类似(Sandy Bridge、Ivy Bridge),要么更加严格(Nehalem、Westmere、Pentium Pro/II/III/4)。在AMD上也存在类似的限制。

来自Agner Fog的优秀优化手册:

Haswell/Broadwell

英特尔和AMD CPU的微架构

§ 10.12 内存数据转发延迟

在特定情况下,处理器可以将内存写操作转发到同一地址的后续读操作中。 内存数据转发可以发生在以下情况下:

  • 当写入64位或更少的数据,并且随后是相同大小和相同地址的读取时,不考虑对齐方式。
  • 当写入128或256位,并且随后是完全对齐的相同大小和相同地址的读取时。
  • 当写入64位或更少的数据,并且随后是包含在写地址范围内的较小的完全对齐的读取时。
  • 当写入任何大小的对齐数据,并且随后是两个半部分、四个四分之一等大小的两个或多个读取,其自然对齐方式在写地址范围内。
  • 当写入128位或256位的对齐数据,并且随后是不跨越8字节边界的64位或更小的读取时。

如果内存块跨越64字节的缓存行边界,则会出现2个时钟周期的延迟。如果所有数据都具有其自然对齐方式,则可以避免此问题。

内存数据转发失败的情况如下:

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

一个失败的内存数据转发比成功的内存数据转发多需要10个时钟周期。如果128或256位的写入数据没有至少对齐16,则惩罚更高,大约为50个时钟周期。

强调部分已添加

Skylake

英特尔和AMD CPU的微架构

§ 11.12 内存数据转发延迟

Skylake处理器在特定情况下可以将内存写操作转发到同一地址的后续读取操作中。与以前的处理器相比,内存数据转发速度快了1个时钟周期。

mov [rsp-16], eax
mov [rsp-12], ebx
mov ecx, [rsp-15]

显示ld_blocks.store_forward PMU计数器确实会递增。这个事件的文档如下:

ld_blocks.store_forward [此事件计算读取操作因为真正的存储锁定码(Block-on-Store)阻止存储转发而被阻止的次数。其中包括以下情况: - 先前的存储与加载冲突(部分重叠)

  • 由于微架构限制,存储转发是不可能的

  • 之前的锁定RMW操作没有被转发

  • 存储具有不转发位设置(不可缓存/页面分割/掩码存储)

  • 使用了所有阻塞存储(主要是栅栏和端口I/O)

这表明当一个只读部分地与最近的先前存储部分重叠时(即使考虑到更早的存储时完全包含在内),存储转发确实会失败。


1
精彩的回答。 - Margaret Bloom
1
@BeeOnRope 一个公平的问题,但可以通过查看计数器来回答。我编写了一个紧密的循环mov [rsp-16], eax; mov [rsp-12], ebx; mov ecx, [rsp-15],然后ld_blocks.store_forward计数器增加了。因此,至少在英特尔公司看来,搜索存储缓冲区被认为是存储转发失败,但可以肯定的是,存储缓冲区中的最后两个条目足以计算负载值。 - Iwillnotexist Idonotexist
哦,很酷 - 而且英特尔在事件的文档中确实使用了“失败”这个词,所以我猜这或多或少解决了它! - BeeOnRope
1
@BeeOnRope 謝謝!我正要自己編輯它,但你比我先完成了!編輯:Haswell i7-4700MQ。 - Iwillnotexist Idonotexist
1
这与书面价值的“一半或四分之一”无关,而是相对于书面价值跨越8字节边界。请注意,64位存储可以转发到任何完全重叠的16位加载。这仅适用于存储转发效率接近最大的情况。最坏情况只需11个周期,而不是需要刷新存储队列以提交到L1D的情况(请参见我的答案上的讨论;这才是Bee真正想问的问题)。 - Peter Cordes
显示剩余2条评论

13

相关:x86上失败的存储到加载转发的成本是多少?对于多个SF停顿未被同时处理的细节有更多说明,但在SF停顿正在进行时可以发生成功的SF。


有序Atom可能能够在不停顿的情况下进行此存储转发。

Agner Fog没有特别提到Atom的这种情况,但与所有其他CPU不同,它可以在从存储到更宽或不同对齐的加载时具有1个时钟周期的存储转发延迟。唯一的例外是在缓存行边界处,Atom表现非常糟糕(即使不涉及存储转发,对于CL-split加载或存储,也会有16个周期的惩罚)。


这个负载能否被存储-转发,还是需要等待前面的两个存储提交到L1?这里存在一个术语问题。许多人会将“这个负载能否被存储-转发”解释为是否可以像满足快速路径存储转发的所有要求一样低延迟地进行,如@IWill的答案中列出的那样(其中所有加载的数据都来自与任何负载重叠的最近存储器,并且其他相对/绝对对齐规则得到满足)。起初我以为你漏掉了第三种可能性,即较慢但仍然(几乎)固定延迟的转发,而无需等待提交到L1D,例如使用刮取整个存储缓冲区(和可能从L1D加载)的机制,在Agner Fog和Intel的优化手册中称为“存储转发失败”。但现在我看到这种措辞是有意的,你确实想问第三个选项是否存在。你可能想把其中一些编辑到你的问题中。总之,Intel x86 CPU 的三个可能选项为:
  1. Intel/Agner将存储转发成功定义为,所有数据仅来自一个最近的存储,并具有低且(几乎)固定的延迟。

  2. 额外的(但有限的)延迟用于扫描整个存储缓冲区并按程序顺序组装正确的字节,并(如果必要或总是?)从L1D加载以提供未被最近存储的任何字节的数据。

    我们不确定是否存在这种选项。

    它还必须等待所有存储数据uop的数据,因为它必须遵守程序顺序。可能会发布一些关于具有未知存储地址的推测执行的信息(例如,猜测它们不重叠),但我忘记了。

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

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


中间选项的证据:

Can x86 reorder a narrow store with a wider load that fully contains it?提出的锁定方案可以在需要将失败的store-forwarding清除到L1D的情况下工作。但由于在实际硬件上没有mfence就无法工作,这强烈表明实际的x86 CPU正在将存储缓冲区的数据与L1D中的数据合并。因此,在这种情况下存在并使用第二种选择。

请参见Linus Torvalds' explanation that x86 really does allow this kind of reordering,他回应了另一个人提出的与该SO问题相同的锁定想法。

我尚未测试store-forwarding失败/停顿惩罚是否可变,但如果不可变,那么它很可能会退回到在最佳情况下的转发不起作用时检查整个存储缓冲区。

希望有人能回答What are the costs of failed store-to-load forwarding on x86?,它正是询问这个问题的。如果我有时间,我会回答。

Agner Fog只提到一种存储转发惩罚的数字,并没有说如果缓存未命中存储在无法转发之前进行,这个数字会更大。 (这会导致很大的延迟,因为由于x86的强有序内存模型,存储必须按顺序提交到L1D。)他也没有提到数据来自1个存储器+ L1D和来自两个或多个存储器的部分情况是不同的,所以我猜在这种情况下它也有效。
我怀疑“失败”的存储转发很常见,值得用晶体管更快地处理它,而不仅仅是清空存储队列并从L1D重新加载。

例如,gcc没有特别避免存储转发停顿的尝试, 它的一些惯用语会导致这种情况(例如,在32位代码中__m128i v = _mm_set_epi64x(a, b);将存储/重新加载到堆栈中,这在大多数CPU上对于大多数情况来说已经是错误的策略,因此出现了该错误报告)。虽然这不是好事,但据我所知,结果通常不会灾难性。

只是为了明确,“在“此负载是否可以进行存储转发,还是需要等待前面的两个存储提交到L1?”中的第三个选项是什么?请注意,我认为存储转发意味着该负载从存储缓冲区满足,但这不限于单个缓冲区。因此,我认为负载从多个先前缓冲存储器中满足是存储转发的一种情况(是的,可能会慢得多)。现在,这可能不是正确的定义,但它隐含在问题标题中。 - BeeOnRope
@BeeOnRope:哦,嗯,是的,这里有一个术语问题。如果我们的意思是“使用最有效的机制存储-转发”,那么 @Iwill 的“不”回答是正确的,并且人们经常使用“存储转发失败”来表示这种情况没有发生。但是现在我重新阅读了你的问题,我发现你询问的不是这个。 - Peter Cordes
是的,这主要是术语问题,但对于我的问题,我需要明确是否需要承诺使用L1缓存。如果存储错过了RAM并且随后的加载以重叠方式击中它们,则与某种较慢但仍来自存储缓冲区方法之间的差异可能会很大。 - BeeOnRope
@BeeOnRope:是的,完全正确。好问题。正在进行更新;我有一些证据表明它不必提交给L1D。 - Peter Cordes
1
@Noah:你的意思是如果分支预测错误了吗?回滚到RAT/ROB状态的先前快照甚至不会尝试保留来自错误路径的指令,即使它们也在正确路径上(具有不同的前置指令)。但是,对于内存消歧问题,对未知地址进行存储(例如使用cmov或将结果加载为存储地址)是一个问题;(https://github.com/travisdowns/uarch-bench/wiki/Memory-Disambiguation-on-Skylake)现代英特尔CPU动态预测加载insn是否重新加载先前的存储;可能会导致单线程代码中的mem_order管道核弹。 - Peter Cordes
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接