divss
的计数被算到后续的指令中,这被称为“skid”。(有关更多详细信息,请参见该评论线程的其余部分。)Xcode 可能类似于 Linux 的 perf
,并使用固定的 cpu_clk_unhalted.thread
计数器来计算 cycles
,而不是可编程计数器之一。这不是一个“精确”的事件(PEBS),因此可能会出现 skids。正如 @BeeOnRope 指出的那样,您可以使用每个周期触发一次的 PEBS 事件(例如 UOPS_RETIRED < 16
)作为固定周期计数器的 PEBS 替代品,从而减少对中断行为的依赖。
但是,计数器在流水线/乱序执行中的基本工作方式也可以解释您所看到的大部分内容。或许可以这样说;你没有展示完整的循环,因此我们无法使用像IACA这样的简单流水线模型或手动使用硬件指南(如http://agner.org/optimize/和Intel的优化手册)来模拟代码。(而且你甚至没有说明你有什么微架构。我猜它是Mac上的Intel Sandybridge家族成员。)
cycles
计数通常是针对等待结果的指令进行计费,而不是通常较慢生成结果的指令。 流水线CPU在尝试读取尚未准备好的结果之前不会停顿。
乱序执行会使这个问题变得非常复杂,但当有一个非常慢的指令(比如经常错过缓存的加载指令)时,这仍然是普遍适用的。当cycles
计数器溢出(触发中断)时,有许多指令正在执行,但只有一个可以与该性能计数器事件相关联的RIP。 这也是中断后执行将恢复的RIP。
那么当引发中断时会发生什么?请参见Andy Glew's answer,其中解释了Intel P6微架构管道中perf-counter中断的内部原理以及为什么(在PEBS之前)它们总是延迟的。Sandybridge系列在这方面类似于P6。
我认为在英特尔CPU上,perf-counter中断的一个合理的心理模型是丢弃尚未分派到执行单元的任何uops。但已经分派的ALU uops将通过管道到达退休(如果没有更年轻的uops被丢弃),而不是被中止,这是有道理的,因为sqrtpd
的最大额外延迟约为16个周期,并且清空存储器队列可能需要更长的时间。(已经退休的挂起存储器无法回滚)。对于尚未退休的加载/存储器,我不知道;至少加载可能会被丢弃。divss
产生其输出时,很容易构建不显示任何计数的循环。如果它在未退休的情况下被丢弃,那么它将是恢复中断后的下一条指令,因此(除了skids之外),你将看到大量的计数。cycles
计数的分布显示了哪些指令在调度器中是最老的未分派指令所花费的时间最长。(或者在遇到前端停顿时,CPU正在尝试获取/解码/发布哪些指令)。请记住,这通常意味着它向您展示正在等待输入的指令,而不是生产速度慢的指令。(嗯,这可能不正确,我没有测试过这个。我通常使用perf stat
来查看微基准测试中整个循环的总体计数,而不是使用perf record
进行统计分析。 addss
和mulss
的延迟高于andps
,因此如果我的提议模型正确,您会期望andps
获得等待其xmm5输入的计数。)
无论如何,一般问题是,当cycles
计数器回绕时,有多个指令同时运行,硬件会“责怪”哪一个?
divss
在生成结果时速度较慢,但它只是一个单一uop指令(与整数div
不同,后者在AMD和Intel上是微码化的)。如果您没有被其延迟或未完全流水线化的吞吐量所限制,它并不比mulss
慢,因为它可以像周围的代码一样重叠执行。divss
/divps
不完全流水线化。例如,在Haswell上,独立的divps
每7个周期就能开始一个。但每个运算只需要10-13个周期来生成结果。所有其他执行单元都是完全流水线化的;能够在每个周期上启动对独立数据的新操作。)divss
。使用常数除以mulss
与倒数常数相比,性能几乎没有区别。(实际上,乱序调度并不完美,即使不是循环传递的情况下,更长的依赖链也会损害一些性能,因为它们需要更多的指令来隐藏所有的延迟并维持最大吞吐量。即乱序核心找到指令级并行性。)
总之,这里的重点是divss
是单个uop,根据周围的代码,它不应该获得太多的cycles
事件计数。
你的个人资料结果可能告诉我们的信息:
divss
不必等待其输入准备就绪。(在 divss
之前的 movaps %xmm3, %xmm5
有时需要一些周期,但 divss
永远不需要。)
我们可能接近于瓶颈 divss
的吞吐量。
涉及 divss
后的 xmm5
的依赖链正在获得一些计数。乱序执行必须工作以同时保持多个独立迭代。
maxss
/ movaps
循环传递的依赖链可能是一个重要的瓶颈。(特别是如果你在 Skylake 上,其中 divss
的吞吐量为每 3 个时钟周期 1 个,但 maxss
的延迟为 4 个时钟周期。并且来自端口 0 和 1 竞争的资源冲突将延迟 maxss。)
movaps
可能是由于它跟随 maxss
,在您展示的循环部分中形成了唯一的循环依赖关系。因此,maxss
确实缓慢产生结果是有道理的。但如果真的是循环依赖关系是主要瓶颈,你会预期看到许多 maxss
的计数,因为它将等待上一次迭代的输入。
但也许 mov-elimination 是“特殊”的,由于某种原因所有计数都被归结到 movaps
? 在 Ivybridge 和更高版本的 CPU 上,寄存器复制不需要执行单元,而是在流水线的发布/重命名阶段处理。
divss
不必等待其输入时,它的计数很少。如果它在产生输出时速度较慢并且被丢弃而没有退役,那么它应该会因周期而获得大量计数吧?在我的SKL实验中,我看到了同样的情况,其中有一个独立的divss
馈送一个循环传递的依赖链。无论如何,我认为已经派遣的指令可能会被允许继续退役如果没有任何年轻的未执行指令。 - Peter Cordesperf
支持cycles:p
和cycles:ppp
(我认为pp
与p
相同),并使用一个在每个周期都会计数的计数器,例如“UOPS_RETIRED <16”,而PEBS(据我所知)则会用事件的详细信息填充一个单独的缓冲区,因此中断行为并不重要(中断仅需要读取数据本身来自PEBS缓冲区)。 - BeeOnRopedivss
这样的长延迟操作,中断可能会延迟,并且当它完成时,如果退役队列为空,则可能立即退役,因此从未出现过?这可能可以通过一些测试来解决 - 但是 PEBS 使中断行为变得相当不重要,因此我不确定是否需要进行深入研究。 - BeeOnRope这是真的吗?已知吗?
是的,这是在Intel x86上使用分析工具时已知的问题。我观察到了它(时间花费被怀疑地分配给看似无辜的指令),包括Linux perf_events和Intel VTune。其他人也在别处报告过。
更好、更诚实的收集结果可视化将汇总每个基本块内的所有样本,并展示与基本块相关联的结果值,而不是其单个指令。虽然不是100%可靠,但更好且更诚实。
还是说我需要使用某些选项才能获得可靠的结果?
我不知道是否有更新的分析硬件,即基于Intel Processor Trace(从Broadwell开始提供,但在Skylake中得到改进)而不是旧的PEBS,会提供更准确的数据。我想人们需要先尝试使用这些工具进行实验。
divps
本身不会计算任何次数,因为它不必等待其输入。这些次数适用于必须等待缓慢的divss
结果的指令。(但这并不是完整的解释;看起来除非您在 Skylake 上,否则应该会瓶颈 divss 吞吐量。而且后面链中的指令有很多计数,不集中在使用divss
结果的第一条指令上。) - Peter Cordes