Xcode Instrument的拆卸时间分析的可靠性

yai*_*chu 8 x86 xcode profiling instruments intel-pmu

我使用Instrument的时间分析器来描述我的代码,并放大到反汇编,这里是结果的片段:

仪器截图

我不希望一条mov指令占23.3%的时间,而div指令几乎什么也没做.这让我相信这些结果是不可靠的.这是真的吗?或者我只是遇到了仪器错误?或者我是否需要使用一些选项来获得可靠的结果?

这个问题是否有任何参考?

Pet*_*des 4

首先,有可能一些真正属于的计数divss被计入后面的指令,这被称为“滑动”。(另请参阅该评论线程的其余部分以获取更多详细信息。)大概 Xcode 就像 Linux 一样perf,并使用固定cpu_clk_unhalted.thread计数器cycles而不是可编程计数器之一。这不是“精确”事件(PEBS),因此有可能发生打滑。 正如 @BeeOnRope 指出的,您可以使用每个周期触发一次的 PEBS 事件(如UOPS_RETIRED < 16)作为固定周期计数器的 PEBS 替代品,从而消除对中断行为的一些依赖。

但是计数器在流水线/无序执行中的基本工作方式也解释了您所看到的大部分内容。或者可能;您没有显示完整的循环,因此我们无法像 IACA 那样在简单的管道模型上模拟代码,也无法使用http://agner.org/optimize/等硬件指南和英特尔优化手册来手动模拟代码。(而且您甚至没有指定您拥有什么微架构。我猜它是 Mac 上 Intel Sandybridge 系列的某个成员)。


计数cycles通常计入正在等待结果的指令而不是通常计入生成结果较慢的指令。 在您尝试读取尚未准备好的结果之前,流水线 CPU 不会停止运行。

无序执行使这一情况变得非常复杂,但是当有一条非常慢的指令(例如缓存中经常丢失的加载)时,通常情况下仍然如此。当cycles计数器溢出(触发中断)时,有许多指令正在运行,但只有一个可以是与该性能计数器事件关联的 RIP。它也是中断后恢复执行的 RIP。

那么当中断产生时会发生什么呢?请参阅Andy Glew对此的回答,其中解释了 Intel P6 微架构管道中性能计数器中断的内部结构,以及为什么(在 PEBS 之前)它们总是被延迟。Sandybridge-family 在这一点上与 P6 类似。

我认为 Intel CPU 上性能计数器中断的合理心理模型是它丢弃任何尚未分派到执行单元的微指令。但是,已分派的 ALU 微指令已经通过管道直至退休(如果没有任何较新的微指令被丢弃),而不是被中止,这是有道理的,因为最大额外延迟约为 16 个周期sqrtpd,并刷新存储队列很容易花费比这更长的时间。(已经退役的待定商店无法回滚)。IDK 关于尚未退役的负载/存储;至少负载可能被丢弃。

divss我的猜测基于这样一个事实:当 CPU 有时等待它产生输出时,很容易构建不显示任何计数的循环。如果它在没有退出的情况下被丢弃,它将是恢复中断时的下一条指令,因此(除了打滑之外)您会看到它的大量计数。

因此,计数的分布向cycles您显示哪些指令花费最多时间,并且是调度程序中尚未调度的最旧指令。(或者在前端停顿的情况下,CPU 在尝试获取/解码/发出时停顿了哪些指令)。请记住,这通常意味着它会向您显示正在等待输入的指令,而不是生成速度较慢的指令。

(嗯,这可能不对,而且我还没有测试这么多。我通常用来perf stat查看微基准中整个循环的总体计数,而不是带有 . 的统计配置文件perf recordaddss并且mulss延迟比 更高andps,所以您会期望andps如果我提出的模型是正确的,则获取等待其 xmm5 输入的计数。)

不管怎样,普遍的问题是,当多个指令同时运行时,当计数器cycles循环时,硬件会“责怪”哪一条指令?


请注意,divss生成结果的速度很慢,但只是一条单微指令(与divAMD 和 Intel 上微编码的整数不同)。如果您没有遇到延迟或非完全流水线吞吐量的瓶颈,那么它不会比它慢,mulss因为它也可以与周围的代码重叠。

( divss/divps不是完全流水线化的。例如,在 Haswell 上,独立执行单元divps可以每 7 个周期启动一次。但每个执行单元只需要 10-13 个周期即可产生结果。所有其他执行单元都是完全流水线化的;能够对独立数据启动新操作每个周期。)

考虑一个大型循环,该循环的瓶颈在于吞吐量,而不是任何循环携带的依赖项的延迟,并且只需divss每 20 条 FP 指令运行一次。使用divss常数而不是mulss倒数常数应该对性能(几乎)没有影响。(实际上,乱序调度并不完美,即使不循环传送,较长的依赖链也会伤害一些,因为它们需要更多的指令来隐藏所有延迟并维持最大吞吐量。即-of-order 核心来找到指令级并行性。)

不管怎样,这里的要点是,这是一个单独的微指令,根据周围的代码,divss它不会为事件获得很多计数是有意义的。cycles


您会在缓存未命中加载中看到相同的效果:如果加载本身必须在寻址模式下等待寄存器,则加载本身通常只获取计数,并且依赖链中使用加载数据的第一条指令获取大量计数。


您的个人资料结果可能告诉我们什么

  • 不必divss等待其输入准备好。(movaps %xmm3, %xmm5beforedivss有时需要一些周期,但thedivss从来不会。)

  • 我们可能会接近吞吐量瓶颈divss

  • xmm5涉及after 的依赖链divss正在得到一些计数。无序执行必须能够同时保持多个独立迭代的运行。

  • maxss/movaps循环携带的依赖链可能是一个重要的瓶颈。(特别是如果您在 Skylake 上,divss吞吐量为每 3 个时钟周期 1 个,但maxss延迟为 4 个周期。端口 0 和 1 竞争造成的资源冲突将延迟 maxss。)


的高计数movaps可能是由于它跟随maxss,形成您显示的循环部分中唯一的循环携带依赖项。因此,maxss产生结果的速度确实很慢,这是合理的。但是,如果它确实是一个循环携带的 dep 链,这是主要瓶颈,那么您会期望看到其自身的大量计数maxss,因为它将等待上次迭代的输入。

但也许 mov-elimination 是“特殊的”,并且由于某种原因所有计数都被计入movaps? 在 Ivybridge 和更高版本的 CPU 上,寄存器副本不需要执行单元,而是在管道的发出/重命名阶段处理