10

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

仪器截图

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

有没有关于这个问题的参考资料?

4

2 回答 2

4

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

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


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

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

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

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

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

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

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

无论如何,一般的问题是,一次飞行中有多个指令,当计数器环绕时,硬件“责备”哪一个?cycles


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

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

考虑一个大循环,它会限制吞吐量,而不是任何循环携带依赖的延迟,并且divss每 20 条 FP 指令只需要运行一次。使用divss常数而不是mulss倒数常数应该(几乎)没有性能差异。(实际上,乱序调度并不完美,更长的依赖链即使在没有循环承载的情况下也会伤害一些,因为它们需要更多的指令来运行以隐藏所有延迟并维持最大吞吐量。即输出-of-order core 来查找指令级并行性。)

无论如何,这里的重点是这是一个单一的 uop ,根据周围的代码divss,它不会为事件获得很多计数是有意义的。cycles


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


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

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

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

  • 涉及xmm5after的依赖链divss得到了一些计数。乱序执行必须同时保持多个独立的迭代运行。

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


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

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

于 2018-02-11T14:19:49.090 回答
1

这是真实的和已知的吗?

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

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

或者我需要使用一些选项来获得可靠的结果吗?

我不知道更新的分析硬件,即基于英特尔处理器跟踪的工具(从 Broadwell 开始提供,但在 Skylake 中改进)而不是旧的 PEBS,是否会提供更准确的数据。我想首先需要尝试使用这些工具。

于 2018-02-11T12:02:53.593 回答