Andy @Krazy Glew 的这句话是关于在执行“正常”指令期间发现的同步异常,例如 mov eax, [rdi] 如果发现 RDI 指向未映射的页面,则会引发 #PF。1 sup> 你希望这不会出错,所以你推迟做任何事情直到退休,以防它处于分支错误预测或早期异常的阴影中。
但是是的,他的回答没有详细说明管道如何针对同步int 陷阱指令进行优化,我们知道在解码时总是会导致异常。陷阱指令在整个指令组合中也非常少见,因此针对它们进行优化并不能节省很多功率;只有做容易的事才值得。
正如 Andy 所说,当前的 CPU 不会重命名特权级别,因此无法推测中断/异常处理程序,因此在看到 int 或 syscall 之后停止获取/解码绝对是明智的。我只是要写int 或“陷阱指令”,但syscall/sysenter/sysret/iret 和其他改变特权的“分支”指令也是如此。而1-byte versions of int 就像int3 (0xcc) 和int1 (0xf1)。有条件的溢出陷阱into 很有趣;对于无陷阱情况下的非可怕表现,可能假定不陷阱。 (当然还有 vmcall 和 VMX 扩展的东西,可能还有 SGX EENTER,可能还有其他东西。但就停止管道而言,我猜所有陷阱指令都是平等的,除了条件into)
我假设像 lfence 一样,CPU 不会推测通过陷阱指令。你是对的,将这些微指令放在管道中是没有意义的,因为int 之后的任何内容肯定会被刷新。
IDK 如果在int 指令在后面变为非推测性之前从 IVT(实模式中断向量表)或 IDT(中断描述符表)获取 int 处理程序的地址 -结尾。可能。 (一些陷阱指令,如syscall,使用 MSR 来设置处理程序地址,因此从那里开始获取代码可能很有用,特别是如果它提前触发 L1i 未命中。这必须权衡看到 @ 的可能性987654349@ 和其他错误路径上的陷阱指令,在分支未命中后。)
错误推测命中陷阱指令的情况可能很少见,因此值得在前端看到陷阱指令后立即开始从 IDT 加载或预取 syscall 入口点,如果前端- end 足够聪明来处理这一切。但它可能不是。将花哨的东西留给微码对于限制前端的复杂性是有意义的。陷阱很少见,即使在 syscall-heavy 工作负载中也是如此。批处理工作以跨用户/内核障碍以更大的块移交是一件好事,因为便宜的syscall 在 Spectre 之后非常非常困难......
所以最迟会在 issue/rename 中检测到一个陷阱(它已经知道如何为(部分)序列化指令停止),并且不会将更多的微指令分配到输出中。无序后端,直到 int 退出并且正在处理异常。
但是在解码中检测到它似乎是可能的,并且不会进一步解码超过肯定发生异常的指令。 (而且我们不知道接下来从哪里获取。)解码器阶段确实知道如何停止,例如用于非法指令陷阱。
假设它是在预解码时提取的
这可能不实用,在完全解码之前你不知道它是int。预解码只是在 Intel CPU 上查找指令长度。我假设int 和syscall 的操作码只是长度相同的众多操作码中的两个。
在硬件中构建以更深入地搜索陷阱指令将花费比预解码更多的功率。 (请记住,陷阱非常少见,早期检测它们大多只能节省电量,因此在将陷阱传递给解码器后停止预解码所节省的电量不会超过寻找它们的电量。
您需要对int 进行解码,以便其微码可以执行并让 CPU 再次开始运行中断处理程序,但理论上是的,您可能会在 传递之后的循环中出现预解码停顿它通过了。
例如,在常规解码器中,分支预测错过的跳转指令被识别出来,因此主解码阶段通过不进一步处理陷阱更有意义。 p>
超线程
当您发现一个档位时,您不只是对前端进行电源门控。您让其他逻辑线程拥有所有循环。
超线程降低了前端在没有后端帮助的情况下从 IDT 指向的内存中获取的价值。如果其他线程没有停止,并且可以在该线程整理其陷阱时从额外的前端带宽中受益,则 CPU 正在做有用的工作。
我当然不会排除从 SYSCALL 入口点获取代码的可能性,因为该地址位于 MSR 中,并且它是在某些工作负载中与性能相关的少数陷阱之一。
我很好奇的另一件事是,一个逻辑核心切换权限级别对另一个核心的性能有多大影响。为了测试这一点,您将构建一些工作负载,这些工作负载会限制您选择的前端问题带宽、后端端口、后端 dep 链延迟或后端在中长距离上查找 ILP 的能力(RS 大小或 ROB 大小)。或组合或其他东西。然后比较在内核上运行的测试工作负载与自身的周期/迭代,共享一个具有紧密dec/jnz 线程、4x pause / dec/jnz 工作负载和在 Linux 下进行 ENOSYS 系统调用的syscall 工作负载的内核。也许还有一个int 0x80 工作负载来比较不同的陷阱。
脚注 1:异常处理,例如正常负载下的 #PF。
(题外话,回复:看似无辜的错误指令,而不是陷阱指令,可在解码器中检测为引发异常)。
您等到提交(退休),因为您不想立即启动昂贵的管道刷新,只是发现该指令处于分支未命中(或较早的错误指令)的阴影中并且不应该首先已经运行(使用那个错误的地址)。让快速分支恢复机制抓住它。
这种等待直到退休的策略(以及一个危险的 L1d 缓存,不会将 L1d 命中的负载值压缩为 0,TLB 表示它有效但没有读取权限)是为什么 Meltdown 和 L1TF漏洞利用适用于某些 Intel CPU。 (http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/)。理解 Meltdown 对理解高性能 CPU 中的同步异常处理策略非常有帮助:标记指令并仅在 它达到退休时执行任何操作是一种很好的廉价策略,因为异常非常罕见。
如果后端中的任何微指令检测到待处理的#PF 或其他异常,让执行单元向前端发信号以停止获取/解码/发布显然不值得这么复杂 . (大概是因为这会更紧密地耦合 CPU 中相距很远的部分。)
并且由于在从分支未命中快速恢复期间来自错误路径的指令可能仍在运行中,因此确保您仅在我们认为当前正确执行路径上出现预期错误时停止前端需要更多追踪。后端中的任何 uop 在某一时刻都被认为是在正确的路径上,但当它到达执行单元的末尾时,它可能不再是正确的路径。
如果您没有进行快速恢复,那么也许值得让后端发送“出现问题”信号以停止前端,直到后端实际发生异常或发现异常正确的路径。
使用 SMT(超线程),当线程检测到它当前正在推测导致故障的(可能正确的)路径时,这可以为其他线程留出更多的前端带宽。
所以这个想法可能有一些优点;我想知道是否有任何 CPU 可以做到这一点?