写入组合缓冲区是不是用于正常写入英特尔上的 WB 内存区域?

Posted

技术标签:

【中文标题】写入组合缓冲区是不是用于正常写入英特尔上的 WB 内存区域?【英文标题】:Are write-combining buffers used for normal writes to WB memory regions on Intel?写入组合缓冲区是否用于正常写入英特尔上的 WB 内存区域? 【发布时间】:2019-04-25 09:55:31 【问题描述】:

Write-combining 缓冲区一直是 Intel CPU 的一项功能,至少可以追溯到 Pentium 4 并且可能更早。基本思想是这些高速缓存行大小的缓冲区将写入收集到同一高速缓存行,因此它们可以作为一个单元进行处理。作为它们对软件性能影响的一个例子,如果你不写完整的缓存行,你可能会遇到reduced performance。

例如,在Intel 64 and IA-32 Architectures Optimization Reference Manual 部分“3.6.10 写入组合”中以以下描述开头(添加了重点):

写入组合 (WC) 通过两种方式提高性能:

• 正在写入 错过一级缓存,它允许多个存储到同一个 在读取该缓存行以获得所有权 (RFO) 之前出现的缓存行 在缓存/内存层次结构中更远。然后剩下的线 被读取,未写入的字节与 返回行中未修改的字节。

• 写组合允许 多个写入被组装并进一步写入缓存中 层次结构为一个单元。这节省了端口和总线流量。节省流量 对于避免对未缓存的部分写入特别重要 记忆。

有六个写入组合缓冲区(在 Pentium 4 和 Intel 具有系列编码 15 的 CPUID 签名的 Xeon 处理器,型号 编码3;有 8 个写组合缓冲区)。其中两个缓冲区 可以写出到更高的缓存级别并释放以供使用 其他写未命中。仅保证四个写组合缓冲区 可以同时使用。 写组合适用于 记忆型WC;它不适用于内存类型UC。

有六个 Intel Core Duo 中每个处理器内核中的写入组合缓冲区和 英特尔酷睿 Solo 处理器。基于英特尔酷睿的处理器 微架构在每个内核中有 8 个写入组合缓冲区。 以 Intel 微架构代号 Nehalem 开头,有 10 个 可用于写入组合的缓冲区。

编写合并缓冲区 用于所有内存类型的存储。他们特别 对于写入未缓存的内存很重要...

我的问题是写入组合是否适用于 WB 内存区域(这是您在用户程序中 99.99% 的时间使用的“正常”内存),当使用普通存储时(即非临时存储以外的任何内容,即您 99.99% 的时间都在使用的商店)。

上面的文字很难准确解释,因为自 Core Duo 时代以来就没有更新过。您有部分说写梳理“适用于 WC 内存,但不适用于 UC”,但当然排除了所有其他类型,如 WB。后来你会说“[WC]对于写入未缓存的内存特别重要”,这似乎与“不适用于 UC 部分”相矛盾。

那么现代英特尔芯片上使用的写入组合缓冲区用于正常存储到 WB 内存吗?

【问题讨论】:

IIRC,我想我在某处读到缓存未命中存储(到 WB 内存)可以提交到等待该行数据到达的 LFB。或等待 RFO。但我可能记错了,因为我不确定这会让核心有效地窥探这些商店以进行商店转发。 @PeterCordes 这也可能使内存排序复杂化,因为正常的存储必须是强排序的,因此不同行的存储被组合到不同的飞行缓冲区中,它对存储顺序设置了一些严格的限制稍后可以使相应的行无效/可见。也许其他订购问题已经暗示了这一点,我不确定。 @Peter - 我发现事情不太可能完全按照哈迪的回答所描述的那样工作,至少对于 WB 地区的普通商店来说。例如,我不认为存储是在 LFB 中上演的,而是在存储缓冲区中,直到它们提交到 L1。 LFB 位于 L1 的另一侧,我认为它们不会被 L1 中的负载窥探。我认为在 LFB 中发生的任何合并并允许释放存储缓冲区条目对于 x86 上的存储排序来说都是非常有问题的,存储间排序会丢失。 说这条线必须保持在排他状态才能工作对我来说没有多大意义:E 或 M 状态通常会作为响应的一部分获得缓存的外部级别,基本上是在数据本身到达的同时。所以我看不到这样一种情况,您将miss存储在一条线上,但不知何故将这条线快速置于E或M中,然后等待一段时间获取数据。我不确定哈迪在他的任何或大部分答案中是否在谈论 WB 地区。 WC 协议的东西显然有不同的工作方式。 嗯,所以我给出的这两个示例(来自 WC 的 movntdqa 和命中 NT 存储的负载)都会在 L1d 中丢失,并且只有在那之后才能进行特殊处理。提交到 LFB 将使存储转发的加载路径涉及 L1d 未命中,然后从 LFB 读取,但这似乎不太可能,除非在存储转发中有一些已知的驼峰,如果读取发生得太晚,则存在一些时间窗口。比从存储缓冲区转发或从 L1d 读取更糟糕。 (但很难衡量调度 -> 如果调度没有受到依赖项的瓶颈,则准备就绪延迟。) 【参考方案1】:

是的,LFB 的写入组合和合并属性支持除 UC 类型之外的所有内存类型。您可以使用以下程序通过实验观察它们的影响。它需要两个参数作为输入:

STORE_COUNT:顺序执行的 8 字节存储的数量。 INCREMENT:连续店铺之间的跨度。

INCREMENT 有 4 个不同的值特别有趣:

64:所有存储都在唯一的高速缓存行上执行。写合并和合并不会生效。 0:所有存储都位于同一高速缓存行和该行内的同一位置。写入合并在这种情况下生效。 8:每 8 个连续存储都指向同一缓存行,但该行内的位置不同。这种情况下写合并生效。 4:连续存储的目标位置在同一高速缓存行内重叠。一些存储可能跨越两个缓存行(取决于STORE_COUNT)。写入合并和合并都会生效。

还有另一个参数ITERATIONS,用于多次重复相同的实验以进行可靠的测量。你可以保持在1000。

%define ITERATIONS 1000

BITS 64
DEFAULT REL

section .bss
align 64
bufsrc:     resb STORE_COUNT*64

section .text
global _start
_start:  
    mov ecx, ITERATIONS

.loop:
; Flush all the cache lines to make sure that it takes a substantial amount of time to fetch them.
    lea rsi, [bufsrc]
    mov edx, STORE_COUNT
.flush:
    clflush [rsi]
    sfence
    lfence
    add rsi, 64
    sub edx, 1
    jnz .flush

; This is the main loop where the stores are issued sequentially.
    lea rsi, [bufsrc]
    mov edx, STORE_COUNT
.inner:
    mov [rsi], rdx
    sfence ; Prevents potential combining in the store buffer.
    add rsi, INCREMENT
    sub edx, 1
    jnz .inner

; Spend sometime doing nothing so that all the LFBs become free for the next iteration.
    mov edx, 100000
.wait:
    lfence
    sub edx, 1
    jnz .wait

    sub ecx, 1
    jnz .loop

; Exit.    
    xor edi,edi
    mov eax,231
    syscall

我推荐以下设置:

使用sudo wrmsr -a 0x1A4 0xf 禁用所有硬件预取器。这样可以确保他们不会干扰(或干扰最小)实验。 将 CPU 频率设置为最大值。这增加了主循环在第一个缓存行到达 L1 之前完全执行并导致 LFB 被释放的可能性。 禁用超线程,因为 LFB 是共享的(至少从 Sandy Bridge 开始,但不是在所有微架构上)。

L1D_PEND_MISS.FB_FULL 性能计数器使我们能够捕捉写入组合对 LFB 可用性的影响。它在 Intel Core 及更高版本上受支持。描述如下:

请求需要 FB(填充缓冲区)条目但存在的次数 没有可用的条目。一个请求包括 加载、存储或软件预取的可缓存/不可缓存需求 说明。

首先运行没有内部循环的代码,并确保L1D_PEND_MISS.FB_FULL 为零,这意味着刷新循环对事件计数没有影响。

下图是STORE_COUNTL1D_PEND_MISS.FB_FULL 总数除以ITERATIONS 的图。

我们可以观察到以下几点:

很明显,正好有 10 个 LFB。 当可以进行写入合并或合并时,L1D_PEND_MISS.FB_FULL 对于任意数量的存储都为零。 当stride为64字节时,当store数大于10时L1D_PEND_MISS.FB_FULL大于零。

稍后您会发现“[WC] 对于写入 未缓存的内存”,似乎与“不适用于 UC 部分”相矛盾。

WC 和 UC 都被归类为不可缓存。所以你可以把这两个语句放在一起来推断WC对于写入WC内存特别重要。

另请参阅:Where is the Write-Combining Buffer located? x86。

【讨论】:

我认为该图可以用您已经做出的观察来解释:这意味着当写入组合或合并成为可能时,LFB 变得更早可用。那里的问题:我认为您是对的,表明较低增量可以更快地获得更多可用行,但这不能简单地解释为它花费更少的时间从内存中返回 1 行(0、4 增量情况) 还是 2 行(8 例)比 10 行(64 例)?您不一定需要调用合并。 现在图表看起来就像我所期望的那样。这不只是告诉我们快速连续存储到 10+ 个缓存行(增量 64 的情况)超过了 10 个 LFB,而存储到 1 或 2(其他情况)不是吗?我实际上开始担心我的问题格式不正确。我希望给定的 LFB 将所有以后的读取或存储请求吸收到同一行,我认为这就是您的图表所显示的。这是否使它在英特尔手册的意义上“写结合”?可能我没有很好的区分这两者。 我误解了这个测试。我认为它正在做正确的事情。基本上它表明正在组合,否则我们希望较小的步幅测试显示相同的峰值。也就是说,在 L1 中未命中的存储不会位于存储缓冲区的头部,而是分配给它们一个填充缓冲区,因此存储缓冲区可以保持耗尽。它还表明,稍后命中相同填充缓冲区的存储可以流入它们而不是阻塞。唯一可以添加的内容是检查resource_stalls.sb,以检查 SB 是否按照我们的想法行事。 @BeeOnRope 我认为我的测试实际上并没有做正确的事情。此外,我现在倾向于一个“不”的答案。因为SFENCE,总是有resource_stalls.sb的摊位。我认为我们应该做的第一件事是确定SFENCE 是如何工作的,即它是在看到第一个存储时阻塞分配还是由存储缓冲区处理。我认为这对于正确解释我的答案中的图表非常重要。我已在SFENCE 上回复了您对我的blog post 的评论。 另外我认为我的测试不能用来证明有10个LFB;那将是一个无效的结论。但是已经知道有 10 个 LFB 对解释结果非常有用。

以上是关于写入组合缓冲区是不是用于正常写入英特尔上的 WB 内存区域?的主要内容,如果未能解决你的问题,请参考以下文章

直写与回写

将字节写入波形文件?

Python文件使用“wb”方式打开,写入内容

为啥 Spark 选择在 shuffle 阶段通过网络发送数据,而不是写入 HDFS 上的某个位置?

OpenGL FBO 与 MRT 写入后台缓冲区

SSD写入很慢是怎么回事