Xcode 仪器拆卸时间分析的可靠性

Reliability of Xcode Instrument's disassembly time profiling

我已经使用 Instrument 的时间分析器分析了我的代码,并放大了反汇编,这是其结果的一个片段:

我不希望 mov 指令占用 23.3% 的时间,而 div 指令几乎不占用任何时间。 这使我相信这些结果是不可靠的。 这是真的吗?或者我只是遇到了一个 Instruments 错误?还是我需要使用一些选项来获得可靠的结果?

这个问题有没有参考扩展?

Is this true and known?

是的,这是 Intel x86 上分析工具的一个已知问题。我已经用 Linux perf_events 和 Intel VTune 观察到它(可疑地分配给看似无辜的指令的时间)。其他人在别处也有报道。

对收集到的结果进行更好、更真实的可视化会汇总每个基本块内的所有样本,并展示与基本块相关联的结果值,而不是其单独的指令。不是 100% 万无一失,但更诚实一点,

Or is there some option I need to use to obtain reliable results?

我不知道更新的分析硬件,即基于 Intel Processor Trace 的工具(从 Broadwell 开始可用,但在 Skylake 中得到改进)而不是旧的 PEBS,是否会提供更准确的数据。我想人们需要先尝试使用此类工具。

首先,一些真正属于 divss 的计数可能会被计入后面的指令,. (Also see the rest of that comment thread for some more details.) Presumably Xcode is like Linux perf, and uses the fixed cpu_clk_unhalted.thread counter for cycles instead of one of the programmable counters. This is not a "precise" event (PEBS), so skids are possible. ,您可以使用每个周期滴答一次的 PEBS 事件(例如 UOPS_RETIRED < 16) 作为固定周期计数器的 PEBS 替代品,消除了对中断行为的一些依赖。

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


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

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

那么当引发中断时会发生什么?请参阅 Andy Glew's answer,它解释了 Intel P6 微体系结构管道中性能计数器中断的内部结构,以及为什么(在 PEBS 之前)它们总是被延迟。 Sandybridge-family 与 P6 类似。

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

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

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

(嗯,这可能不对,我还没有测试这么多。我通常使用 perf stat 查看整个循环的总计数在微基准测试中,没有 perf record 的统计配置文件。addssmulss 的延迟比 andps 高,因此您希望 andps 获得等待计数如果我提出的模型是正确的,它的 xmm5 输入。)

无论如何,一般的问题是,同时执行多个指令,当 cycles 计数器环绕时,硬件 "blame" 执行哪一个?


请注意,divss 生成结果的速度很慢,但它只是一条单 uop 指令(与在 AMD 和 Intel 上进行微编码的整数 div 不同)。如果您不对其延迟或未完全流水线化的吞吐量造成瓶颈,it's not slower than mulss 因为它也可以与周围的代码重叠。

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

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

无论如何,这里的要点是 divss 是单个 uop,根据周围的代码,cycles 事件不会得到很多计数是有道理的。


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


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

  • divss 不必等待其输入准备就绪。 (divss 之前的 movaps %xmm3, %xmm5 有时需要一些周期,但 divss 永远不会。)

  • 我们可能接近 吞吐量 divss

  • 的瓶颈
  • divss 之后涉及 xmm5 的依赖链得到一些计数。乱序执行必须同时进行多次独立迭代。

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


movaps 的高计数可能是由于它在 maxss 之后,在您显示的循环部分中形成了唯一的循环携带依赖项。因此,maxss 产生结果的速度确实很慢,这是有道理的。但如果它真的是一个循环携带的 dep 链是主要的瓶颈,你会期望在 maxss 本身看到很多计数,因为它会等待上一次迭代的输入。

但也许 mov-elimination 是 "special",并且由于某种原因所有计数都被计入 movaps?在 Ivybridge 及之后 CPUs,.