在预测现代超标量处理器上的操作延迟时需要考虑哪些因素以及如何手动计算它们?

ima*_*ett 8 assembly pipeline latency x86-64 superscalar

我希望能够手动预测任意算术的长度(即没有分支或内存,尽管这也很好)x86-64汇编代码将采用特定的体系结构,考虑到指令重新排序,超标量,延迟,消费者价格指数等

什么/描述必须遵循的规则才能实现这一目标?


我想我已经找到了一些初步规则,但是我没有找到任何关于将任何示例代码分解为这个详细程度的引用,所以我不得不做一些猜测.(例如,英特尔优化手册甚至几乎没有提到指令重新排序.)

至少,我正在寻找(1)确认每条规则是正确的,或者是每条规则的正确陈述,以及(2)我可能忘记的任何规则的列表.

  • 每个循环发出尽可能多的指令,从当前循环开始按顺序开始,并且可能与重新排序缓冲区大小一样远.
  • 如果出现以下情况,可以在给定周期发出指令:
    • 没有影响其操作数的指令仍在执行中.和:
    • 如果它是浮点指令,则它之前的每个浮点指令都被发出(浮点指令具有静态指令重新排序).和:
    • 该循环有一个功能单元可用于该指令.每个(?)功能单元是流水线的,这意味着它可以在每个周期接受1个新指令,并且对于给定功能类的CPI,总功能单元的数量是1/CPI(这里模糊不清:可能是例如addps并且subps使用相同的功能) unit?我如何确定?).和:
    • 4此循环已经发出少于超标量宽度(通常)指令的数量.
  • 如果不能发出指令,则处理器不会发出任何称为"停顿"的条件.

例如,请考虑以下示例代码(计算交叉产品):

shufps   xmm3, xmm2, 210
shufps   xmm0, xmm1, 201
shufps   xmm2, xmm2, 201
mulps    xmm0, xmm3
shufps   xmm1, xmm1, 210
mulps    xmm1, xmm2
subps    xmm0, xmm1
Run Code Online (Sandbox Code Playgroud)

我试图预测Haswell的延迟看起来像这样:

; `mulps`  Haswell latency=5, CPI=0.5
; `shufps` Haswell latency=1, CPI=1
; `subps`  Haswell latency=3, CPI=1

shufps   xmm3, xmm2, 210   ; cycle  1
shufps   xmm0, xmm1, 201   ; cycle  2
shufps   xmm2, xmm2, 201   ; cycle  3
mulps    xmm0, xmm3        ;   (superscalar execution)
shufps   xmm1, xmm1, 210   ; cycle  4
mulps    xmm1, xmm2        ; cycle  5
                           ; cycle  6 (stall `xmm0` and `xmm1`)
                           ; cycle  7 (stall `xmm1`)
                           ; cycle  8 (stall `xmm1`)
subps    xmm0, xmm1        ; cycle  9
                           ; cycle 10 (stall `xmm0`)
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 7

相关:每个汇编指令需要多少个CPU周期?是对每个指令的吞吐量与延迟的良好介绍,以及它对多个指令序列的意义.


这称为静态(性能)分析.维基百科说(https://en.wikipedia.org/wiki/List_of_performance_analysis_tools)AMD的AMD CodeXL有一个"静态内核分析器"(即用于计算内核,又称循环).我从来没有尝试过.

英特尔还提供了一个免费工具,用于分析循环将如何通过Sandybridge系列CPU中的管道:IACA是什么以及如何使用它?

IACA也不错,但有错误(例如shldSandybridge上的数据错误,最后我检查过,它不知道Haswell/Skylake可以保持索引寻址模式微融合以获得某些指令.但是现在可能会改变,因为英特尔的在他们的优化手册中添加了详细信息.)IACA也无益于计算前端uops,看看你有多接近瓶颈(它只是给你非融合域的uop计数).


静态分析通常非常好,但绝对要通过性能计数器进行分析来检查.见罐86的MOV真的是"免费"?为什么我不能重现这个呢?有关分析简单循环以研究微架构特征的示例.


基本阅读:

Agner Fog的 微指南指南(第2章:序执行)解释了依赖链和乱序执行的一些基础知识.他的"优化装配"指南具有更好的入门级和高级性能.

他的微型指南的后续章节涵盖了Nehalem,Sandybridge,Haswell,K8/K10,Bulldozer和Ryzen等CPU中的管道细节.(和Atom/Silvermont/Jaguar).

Agner Fog的指令表(电子表格或PDF)通常也是指令延迟/吞吐量/执行端口故障的最佳来源.

David Kanter的微观分析文档非常好,有图表.例如https://www.realworldtech.com/sandy-bridge/,https://www.realworldtech.com/haswell-cpu/https://www.realworldtech.com/bulldozer/.

另请参阅x86标记wiki其他性能链接.

我还试着解释了CPU内核如何在这个答案中找到并利用指令级并行性,但我认为你已经掌握了那些与调优软件相关的基础知识.我确实提到了SMT(超线程)如何作为一种将更多ILP暴露给单个CPU内核的方法.


在英特尔术语中:

  • "问题"是指将uop发送到核心的无序部分; 随着注册重命名,这是前端的最后一步.问题/重命名阶段通常是管道中最窄的一点,例如自Core2以来英特尔的4宽.(由于SKL改进的解码器和uop-cache带宽,以及后端和缓存带宽的改进,后来像Haswell,特别是Skylake这样的搜索实际上在实际代码中经常非常接近.)这是融合域uops :micro-fusion允许你通过前端发送2个uop,只占用一个ROB条目.(我能够在Skylake上构建一个循环,每个时钟维持7个未融合域的uop).另请参见http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/ re:无序窗口大小.

  • "dispatch"表示调度程序将uop发送到执行端口.只要所有输入都准备就绪,并且相关的执行端口可用,就会发生这种情况. 如何安排x86 uops?.调度发生在"未融合"域中; 在OoO调度程序(aka Reservation Station,RS)中单独跟踪微融合微指令.

许多其他计算机体系结构文献在相反的意义上使用这些术语,但这是您将在英特尔优化手册中找到的术语,以及硬件性能计数器的名称,如uops_issued.anyuops_dispatched_port.port_5.


确切的任意算术x86-64汇编代码需要多长时间

由于OoO exec,它也取决于周围的代码

subps在CPU开始运行后续指令之前,您的最终结果不必准备就绪.延迟仅适用于需要该值作为输入的后续指令,而不适用于整数循环等等.

有时吞吐量才是最重要的,无序的exec可以隐藏多个独立的短依赖链的延迟.(例如,如果你对大量多个向量的每个元素做同样的事情,那么多个交叉产品可以同时在飞行中.)即使按程序顺序,你最终也会在飞行中进行多次迭代在完成下一个迭代之前,你完成了所有的迭代.(如果OoO exec很难在HW中进行所有重新排序,则软件流水线可以帮助实现高延迟循环体.)

短块需要分析三个主要维度

您可以根据这三个因素近似表征一小段非分支代码.通常只有其中一个是给定用例的瓶颈.通常你会看到一个块,你将用作循环的一部分,而不是整个循环体,但是OoO exec通常运行得很好,你可以将这些数字加起来用于几个不同的块,如果它们是不久,OoO窗口大小阻止找到所有ILP.

  • 从每个输入到输出的延迟.查看从每个输入到每个输出的依赖关系链上的指令.例如,一个选择可能需要一个输入才能更快地准备好.
  • CPU 数量(用于前端吞吐量瓶颈),Intel CPU上的融合域.例如,Core2和更高版本理论上可以在每个时钟发出/重命名4个融合域uop到无序调度器/ ROB中.Sandybridge系列通常可以在实践中使用uop缓存和循环缓冲区实现这一点,尤其是具有改进的解码器和uop-cache吞吐量的Skylake.
  • 每个后端执行端口(未融合域)的uop计数.例如,重载代码通常会在Intel CPU上的端口5上出现瓶颈.英特尔通常只发布吞吐量数据,而不是端口故障,这就是为什么你必须查看Agner Fog的表(或IACA输出)来做任何有意义的事情,如果你不是只重复相同的指令数十亿次.

    通常,您可以假设最佳情况下的调度/分发,使用可以在其他端口上运行的uops,而不是经常窃取繁忙端口,但它确实发生了一些.(如何安排x86 uops,确切地说?)

    看消费者价格指数是不够的 ; 两个CPI = 1指令可能会也可能不会竞争同一个执行端口.如果他们不这样做,他们可以并行执行.例如,Haswell只能psadbw在端口0上运行(5c延迟,1c吞吐量,即CPI = 1),但它是单个uop,因此1 psadbw+ 3 add指令的混合可以支持每个时钟4个指令.英特尔CPU中的3个不同端口上有向量ALU,其中一些操作在所有3个(例如布尔值)上复制,有些仅在一个端口上复制(例如在Skylake之前移位).

有时候你可以提出几种不同的策略,一种可能是较低的延迟,但需要花费更多的时间.一个典型的例子是乘以常数,imul eax, ecx, 10(1 uop,英特尔3c延迟)与lea eax, [rcx + rcx*4]/ add eax,eax(2 uop,2c延迟).现代编译器倾向于选择2 LEA而不是1 IMUL,尽管铿锵达3.7赞成IMUL,除非它只用一条其他指令完成工作.

请参阅在某个位置或更低位置计算设置位的有效方法是什么?有关实现函数的几种不同方法的静态分析示例.

另请参阅为什么mulss在Haswell上只需要3个周期,与Agner的指令表不同?对于静态分析的另一个总结,以及关于展开多个累加器以减少的一些巧妙的东西(最终比你从问题标题中猜出的更详细).

每个(?)功能单元都是流水线的

分频器在最近的CPU中是流水线的,但没有完全流水线化.(FP除以单uop,所以如果你divps混合使用几十个mulps/ addps,如果延迟无关紧要,它可以产生可忽略的吞吐量影响:浮点除法与浮点乘法. rcpps+牛顿迭代是更糟糕的吞吐量和大约相同的延迟.

其他所有产品都在主流英特尔CPU上完全流水线化; 单个uop的多周期(倒数)吞吐量.(变量计数整数移位shl eax, cl,因为它们的3个uop的吞吐量低于预期,因为它们通过标志合并的uops创建了依赖关系.但是如果你通过FLAGS打破这种依赖关系add,你可以获得更好的吞吐量和延迟.)

在Ryzen之前的AMD上,整数乘数也只是部分流水线.例如Bulldozer imul ecx, edx只有1 uop,但有4c延迟,2c吞吐量.

Xeon Phi(KNL)也有一些不完全流水线的shuffle指令,但它往往会在前端(指令解码)而不是后端出现瓶颈,并且确实有一个小的缓冲区+ OoO exec功能来隐藏 - 泡沫.

如果它是一个浮点指令,则它之前的每个浮点指令都被发出(浮点指令有静态指令重新排序)

没有.

也许你读过Silvermont,它没有为FP/SIMD做OoO exec,只有整数(有一个小的~20 uop窗口).也许一些ARM芯片也是如此,NEON的调度程序更简单?我对ARM uarch细节了解不多.

像P6/SnB系列这样的主流大核微体系结构,以及所有AMD OoO芯片,对于SIMD和FP指令都执行与整数相同的OoO执行.AMD CPU使用单独的调度程序,但英特尔使用统一调度程序,因此其完整大小可应用于查找整数或FP代码中的ILP,无论当前运行的是哪一个.

甚至基于silvermont的Knight's Landing(在Xeon Phi)也为SIMD执行OoO执行.

x86通常对指令排序不是很敏感,但uop调度不进行关键路径分析.所以它有时可以帮助首先将指令放在关键路径上,这样它们就不会等待其输入准备就绪,而其他指令在该端口上运行,当我们得到需要结果的指令时,导致更大的停顿关键路径.(也就是说,这就是它的关键路径.)

我试图预测Haswell的延迟看起来像这样:

是的,看起来不错. shufps在端口5上addps运行,在p1上mulps运行,在p0或p1 上运行.Skylake放弃了专用的FP-add单元并在p0/p1上的FMA单元上运行SIMD FP add/mul/FMA,均具有4c延迟(Haswell中3/5/5上升/下降,或3/3/5 in) Broadwell微架构).

这是为什么在SIMD矢量中保持整个XYZ方向矢量通常很糟糕的一个很好的例子. 保持一个X数组,一个Y数组和一个Z数组,可以让你并行地进行4个交叉产品而不需要任何改组.

SSE标签的wiki有一个链接到这些幻灯片:SIMD在Insomniac的游戏(GDC 2015) ,其覆盖了数组的,结构与结构-的阵列问题三维向量,为什么它往往是错误的总是试图SIMD单个操作而不是使用SIMD并行执行多个操作.


归档时间:

查看次数:

268 次

最近记录:

6 年,9 月 前