为什么仅在存在存储初始化循环时才计算用户模式 ​​L1 存储未命中事件?

2023-11-24

Summary

考虑以下循环:

loop:
movl   $0x1,(%rax)
add    $0x40,%rax
cmp    %rdx,%rax
jne    loop

where rax被初始化为大于 L3 缓存大小的缓冲区的地址。每次迭代都会对下一个缓存行执行存储操作。我预计从 L1D 发送到 L2 的 RFO 请求数量或多或少等于访问的缓存行数量。问题是,这似乎只是当我计算内核模式事件时的情况,即使程序在用户模式下运行,除了我下面讨论的一种情况。缓冲区的分配方式似乎并不重要(.bss、.data 或来自堆)。

Details

我的实验结果如下表所示。所有实验都是在禁用超线程并启用所有硬件预取器的处理器上执行的。

我测试了以下三种情况:

  • 没有初始化循环。也就是说,在上面所示的“主”循环之前不会访问缓冲区。我将把这个案例称为NoInit。在这种情况下只有一个循环。
  • 首先使用每个高速缓存行一个加载指令来访问缓冲区。一旦所有的线都被触及,就会执行主循环。我将把这个案例称为LoadInit。在这种情况下有两个循环。
  • 首先使用每个高速缓存行一个存储指令来访问缓冲区。一旦所有的线都被触及,就会执行主循环。我将把这个案例称为StoreInit。在这种情况下有两个循环。

下表显示了 Intel CFL 处理器上的结果。这些实验是在 Linux 内核版本 4.4.0 上进行的。

enter image description here

下表显示了 Intel HSW 处理器上的结果。请注意,事件L2_RQSTS.PF_HIT, L2_RQSTS.PF_MISS, and OFFCORE_REQUESTS.ALL_REQUESTS没有记录 HSW。这些实验是在 Linux 内核版本 4.15 上进行的。

enter image description here

每个表的第一列包含性能监控事件的名称,其计数显示在其他列中。在列标签中,字母U and K分别表示用户模式和内核模式事件。对于有两个循环的情况,数字 1 和 2 分别用于指代初始化循环和主循环。例如,LoadInit-1K表示初始化循环的内核模式计数LoadInit case.

表中显示的值按缓存行数进行标准化。它们也按如下颜色编码。绿色越深,相对于同一表中所有其他单元格的值就越大。但是,CFL 表的最后三行和 HSW 表的最后两行没有进行颜色编码,因为这些行中的某些值太大。这些行被涂成深灰色,表明它们没有像其他行那样进行颜色编码。

我期望用户模式的数量L2_RQSTS.ALL_RFO事件数等于访问的缓存行数(即标准化值 1)。该事件在手册中描述如下:

计算 L2 的 RFO(读取所有权)请求总数 缓存。 L2 RFO 请求包括 L1D 需求 RFO 未命中以及 L1D RFO 预取。

它说L2_RQSTS.ALL_RFO不仅可以对来自 L1D 的需求 RFO 请求进行计数,还可以对 L1D RFO 预取进行计数。但是,我观察到事件计数不受两个处理器上是否启用或禁用 L1D 预取器的影响。但即使 L1D 预取器可能生成 RFO 预取,事件计数也应至少与访问的高速缓存行数一样大。从两个表中可以看出,只有这样的情况StoreInit-2U。同样的观察结果适用于表中显示的所有事件。

但是,事件的内核模式计数大约等于用户模式的预期计数。这与例如MEM_INST_RETIRED.ALL_STORES (or MEM_UOPS_RETIRED.ALL_STORES在 HSW 上),其按预期工作。

由于PMU计数器寄存器的数量有限,我不得不将所有实验分为四个部分。特别是,内核模式计数是从与用户模式计数不同的运行中产生的。计算什么内容并不重要。我认为告诉您这一点很重要,因为这解释了为什么相同事件的某些用户模式计数比内核模式计数稍大。

深灰色显示的事件似乎多了。第 4 代和第 8 代 Intel 处理器规格手册确实提到(分别是问题 HSD61 和 111):OFFCORE_REQUESTS_OUTSTANDING.DEMAND_RFO可能会多算。但这些结果表明,它可能被多次高估,而不仅仅是几个事件。

还有其他有趣的观察结果,但它们与问题无关,即:为什么 RFO 计数不符合预期?


您没有标记您的操作系统,但我们假设您使用的是 Linux。这些东西在另一个操作系统上会有所不同(甚至可能在同一操作系统的各种变体中)。

在对未映射页面进行读取访问时,内核页面错误处理程序会映射到系统范围的共享零页面,并具有只读权限。

This explains columns LoadInit-1U|K: even though your init load is striding over a virtual area of 64 MB performing loads, only a single physical 4K page filled with zeros is mapped, so you get approximately zero cache misses after the first 4KB, which rounds to zero after your normalization.1

在对未映射页面或只读共享零页面进行写访问时,内核将代表进程映射一个新的唯一页面。这个新页面保证被清零,因此除非内核有一些已知的零页面挂在周围,否则这涉及到将页面清零(有效地)memset(new_page, 0, 4096))在映射之前。

这很大程度上解释了除了StoreInit-2U|K。在这些情况下,即使看起来用户程序正在执行所有存储,但内核最终会执行所有艰苦工作(除了每页一个存储),因为当用户进程在每个页面中发生故障时,内核会写入零它的副作用是将所有页面放入 L1 缓存中。当故障处理程序返回时,该页面的触发存储和所有后续存储将命中 L1 缓存。

它仍然没有完全解释 StoreInit-2。正如评论中所阐明的,K 列实际上包括用户计数,这解释了该列(减去用户计数后,每个事件的用户计数大约为零,如预期)。剩下的困惑是为什么L2_RQSTS.ALL_RFO不是 1,而是一些较小的值,例如 0.53 或 0.68。也许事件计数不足,或者我们缺少一些微架构效果,例如防止 RFO 的预取类型(例如,如果在存储之前通过某种类型的加载操作将行加载到 L1 中) ,RFO 不会发生)。你可以尝试包括其他L2_RQSTSevents 查看缺失的事件是否出现在那里。

变化

不需要在所有系统上都这样。当然,其他操作系统可能有不同的策略,但即使是 x86 上的 Linux 也可能根据各种因素表现不同。

例如,您可能会分配到 2 MiB,而不是 4K 零页巨大的零页。这将改变基准,因为 2 MiB 不适合 L1,因此 LoadInit 测试可能会在第一个和第二个循环的用户空间中显示缺失。

更一般地说,如果您使用大页面,页面错误粒度将从 4 KiB 更改为 2 MiB,这意味着只有一小部分归零页面会保留在 L1 和 L2 中,因此您会遇到 L1 和 L2 未命中,如您所料。如果你的内核曾经实施过故障规避对于匿名映射(或您正在使用的任何映射),它可能会产生类似的效果。

另一种可能性是内核可能在后台清零页面,因此准备好零页面。这将从测试中删除 K 计数,因为在页面错误期间不会发生清零,并且可能会将预期的未命中添加到用户计数中。我不确定 Linux 内核是否曾经这样做过或者有选项这样做,但是有周围漂浮着斑块。其他操作系统(例如 BSD)已经做到了这一点。

RFO预取器

关于“RFO 预取器” - RFO 预取器并不是通常意义上的真正预取器,它们与可以关闭的 L1D 预取器无关。据我所知,L1D 中的“RFO 预取”只是指在计算其地址时(即,当存储数据 uop 执行时)但在其退出之前,为 (a) 存储发送 RFO 请求,或者 (b)对于存储缓冲区中接近但尚未到达存储缓冲区头部的存储。

显然,当存储到达缓冲区的头部时,就需要发送 RFO,并且您不会将其称为预取 - 但为什么不也发送一些对从头部开始的第二个存储的请求,等等(情况b)?或者为什么不知道存储地址后立即检查 L1D(就像加载一样),然后在未命中时发出推测性 RFO 预取?这些可能称为 RFO 预取,但它们与普通预取不同,因为核心knows所请求的地址:这不是猜测。

There is如果另一个核心在该核心有机会从中写入之前发送该行的 RFO,则获取除当前头之外的其他行可能会浪费工作:在这种情况下,该请求是无用的,只会增加一致性流量。因此,如果存储缓冲区预取过于频繁地失败,则有一些预测器可能会减少该预取。还可能存在这样的猜测:存储缓冲区预取可能会向尚未退休的初级存储发送请求,如果存储最终位于错误路径上,则会产生无用的请求。我实际上不确定当前的实现是否可以做到这一点。


1 This behavior actually depends on the details of the L1 cache: current Intel VIPT implementations allow multiple virutal aliases of the same single line to all live happily in L1. Current AMD Zen implementations use a different implementation (micro-tags) which don't allow the L1 to logically contain multiple virtual aliases, so I would expect Zen to miss to L2 in this case.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么仅在存在存储初始化循环时才计算用户模式 ​​L1 存储未命中事件? 的相关文章

  • 获取比较指令的值

    据我了解 cmp 指令将设置标志寄存器中的一些位 然后 您可以使用 jle jnp 等指令基于这些指令进行分支 我想知道如何从比较中恢复整数值 示例 以下是有效的 c 语法 y x a gt 13 因此 a 与 13 进行比较 得到 tru
  • CPU缓存性能。存储未命中与加载未命中

    我使用 perf 作为基本事件计数器 我正在开发一个遭受数据缓存存储未命中的程序 其中比例高达80 我知道缓存原则上是如何工作的 它会在各种未命中情况下从内存加载 并在需要时从缓存中删除数据 我不明白的是 存储未命中和加载未命中之间有什么区
  • 现代缓存中的方式预测

    我们知道 就缓存命中时间而言 直接映射缓存优于集合关联缓存 因为不涉及特定标签的搜索 另一方面 组关联缓存通常比直接映射缓存具有更好的命中率 我读到 现代处理器试图通过使用一种称为路径预测的技术来结合两者的优点 他们预测给定集合中最有可能发
  • 如何在 Ubuntu 14.04 LTS 中安装 ia32-libs (Trusty Tahr)

    我昨天安装了 Ubuntu 14 04 Trusty Tahr 一切看起来都还好 但是当我尝试编译一些C代码时 我遇到了以下错误 该错误似乎是由于操作系统缺乏 32 位架构支持造成的 错误输出如下 usr bin ld i386 archi
  • C/C++ 中的简单“Hello World”内联汇编语言程序

    我使用 devcpp 和 borland c 编译器 asm mov ax 4 I O Func mov bx 1 Output func mov cx name address of the string mov dx 6 length
  • 为什么当设置为 TLS 选择器时,ES 和 DS 在 64 位内核上最终会归零?

    下面的 32 位程序调用set thread area 2 http linux die net man 2 set thread area在 GDT 中创建一个条目 该条目旨在用于 TLS 通常将结果选择器放入FS or GS并成功使用
  • 使用 gdb 调试反汇编库

    在Linux和Mac OS X中可以使用strapi和next来调试应用程序而无需调试信息 在 Mac OS X 上 gdb 显示在库内部调用的函数 尽管有时会在每个 stepi 指令中推进多个汇编程序指令 在 Linux 上 当我进入动态
  • 为什么这个“std::atomic_thread_fence”起作用

    首先我想谈一下我对此的一些理解 如有错误请指正 a MFENCE在x86中可以保证全屏障 顺序一致性可防止 STORE STORE STORE LOAD LOAD STORE 和 LOAD LOAD 重新排序 这是根据维基百科 https
  • 在 x86-64 CPU 上通过交叉修改代码重现意外行为

    Question 对于可能在 x86 或 x86 x64 系统上触发意外行为的交叉修改代码有哪些想法 在这些系统中 交叉修改代码中的所有操作均已正确完成 但在执行处理器之前执行序列化指令除外修改代码 如下所述 我有一个 Core 2 Duo
  • 用于预乘 ARGB 的 SSE alpha 混合

    我正在尝试编写一个支持 SSE 的 alpha 合成器 这就是我想出的 首先 混合两个 4 像素向量的代码 alpha blend two 128 bit 16 byte SSE vectors containing 4 pre multi
  • “rep stos”x86 汇编指令序列有什么作用?

    我最近偶然发现了以下汇编指令序列 rep stos dword ptr edi For ecx重复 存储内容eax到哪里edi指向 递增或递减edi 取决于方向标志 每次 4 个字节 通常 这用于memset型操作 通常 该指令简单地写成r
  • 大会,你好世界问题

    我正在 Linux 上学习 asm noobuntu 10 04 我得到了以下代码 http asm sourceforge net intro hello html http asm sourceforge net intro hello
  • 程序集比较标志理解

    我正在努力理解汇编程序中的以下代码片段 if EAX gt 5 EBX 1 else EBX 2 在汇编程序中 可以写如下 根据我的书 模拟jge操作说明 https www felixcloutier com x86 jcc您通常会使用
  • 我们如何计算这段代码片段中缓存的读取/未命中次数?

    鉴于我目前正在学习的这本教科书中的代码片段 Randal E Bryant David R O Hallaron 计算机系统 程序员的视角 第 3 版 2016 年 Pearson 全球版 因此本书的练习可能是错误的 for i 31 i
  • 在 x86 程序集中存储大量布尔值的最佳方法是什么?

    最近我一直在处理充满布尔值的大型数组 目前 我将它们存储在 bss部分有一个 space指令 它允许我创建字节数组 但是 由于我只需要存储布尔值 因此我希望从数组中逐位读取和写入数据 目前 我能想到的最好方法是有一个 space指令所需存储
  • 为什么我的代码显示垃圾?

    当我也想打印列表中的每个数字时 我的代码显示垃圾 有什么问题吗 输出应如下所示 给定的数组是 2G 4 PT为什么这是垃圾总数是 7 Code ASSUME CS CODE DS DATA SS STK ORG 0000H DATA SEG
  • 整数溢出问题

    我不断遇到整数溢出问题 我不知道如何解决它 有人可以帮忙吗 edx 包含 181 eax 包含 174 xor eax edx mov edx 2 div edx 假设你谈论的是x86 div edx这实际上没有意义 32位div将edx
  • movzbl(%rdi, %rcx, 1), %ecx 在 x86-64 汇编中意味着什么?

    我想我明白 movzbl rdi rcx 1 ecx 意思是 将零扩展字节移至长整型 并表示将 ecx 扩展为 32 位 但我不完全确定语法 rdi rcx 1 指的是什么 我在某处看到该语法指的是 Base Index Scale 但我找
  • 英特尔的最后分支记录功能是英特尔处理器独有的吗?

    最后分支记录是指存储与最近执行的分支相关的源地址和目标地址的寄存器对 MSR 的集合 它们受英特尔酷睿 2 英特尔至强和英特尔凌动处理器系列的支持 http css csail mit edu 6 858 2012 readings ia3
  • CISC 机器 - 它们不只是将复杂指令转换为 RISC 吗?

    也许我在架构上存在误解 但如果机器有 比如说 乘法指令 该指令是否未转换为更小的指令 或者过于复杂以至于最终与等效的 RISC 指令具有相同的速度 乘法是一个不好的例子 它在两种体系结构中都是一条指令 将上面的 乘法 替换为 CISC 中更

随机推荐