c-为什么sqrtsd指令的延迟会根据输入而变化?英特尔处理器



英特尔内部指南中指出,名为"sqrtsd"的指令的延迟为18个周期。

我用自己的程序测试了它,例如,如果我们将0.15作为输入,它是正确的。但当我们取256(或任何2^x)时,延迟只有13。为什么?

我的一个理论是,由于13是"sqrtss"的延迟,它与"sqrtsd"相同,但在32位浮点上完成,因此处理器可能足够聪明,能够理解taht 256可以适应32位,因此使用该版本,而0.15需要完整的64位,因为它不能以有限的方式表示。

我使用内联汇编来完成这项工作,这里是用gcc-O3和-fno树矢量化编译的relveant部分。

static double sqrtsd (double x) {
double r;
__asm__ ("sqrtsd %1, %0" : "=x" (r) : "x" (x));
return r;
}

SQRT*和div*是现代Intel/AMD CPU上仅有的两条具有数据依赖吞吐量或延迟的"简单"ALU指令(单个uop,而不是微码分支/循环)。(不计算加法/乘法/fma中的非正规FP值或亚正规FP值的微码辅助)。其他一切都很固定,所以无序的uop调度机制不需要等待某个周期的结果准备好的确认,它只知道会的。

和往常一样,英特尔的内部指南提供了一个过于简化的性能图片。对于Skylake上的双倍精度,实际延迟不是固定的18个周期。(根据你选择引用的数字,我假设你有一个Skylake。)

div/sqrt很难实现;即使在硬件方面,我们能做的最好的事情也是一个迭代的精化过程。一次细化更多的比特(自Broadwell以来的基数为1024的除法器)可以加快速度(请参阅关于硬件的问答)但它仍然足够慢,早期输出被用来加速简单的情况(或者,加速机制只是跳过现代CPU上所有零尾数的设置步骤,这些CPU具有部分流水线的div/sqrt单元。旧的CPU的吞吐量=FPdiv/sqr的延迟;该执行单元更难流水线。)


https://www.uops.info/html-instr/VSQRTSD_XMM_XMM_XMM.html显示Skylake SQRTSD的周期延迟可以从13到19不等。SKL(客户端)编号仅显示13个周期延迟,但我们可以从详细的SKL-vsqrtsd页面中看到,他们只在input=0的情况下进行了测试。SKX(服务器)数字显示13-19周期延迟。(本页提供了他们使用的测试代码的详细分类,包括测试的二进制位模式。)在非VEXsqrtsd xmm, xmm页上进行了类似的测试(客户端核心仅为0)。:/

InstLatx64结果显示,Skylake-X(使用与Skylake客户端相同的内核,但启用了AVX512)上的最佳/最坏情况延迟为13到18个周期。

Agner Fog的指令表显示了Skylake上15-16个周期的延迟。(Agner通常使用一系列不同的输入值进行测试。)他的测试自动化程度较低,有时与其他结果不完全匹配。

是什么让一些案件快速审理

请注意,大多数ISAs(包括x86)使用二进制浮点:
位表示值为线性有效位(也称尾数)乘以2exp和符号位。

现代英特尔似乎只有两种速度(至少是Haswell)(请参阅评论中与@harold的讨论。)例如,即使是2的幂也都很快,比如0.25、1、4和16。这些具有表示1.0的平凡尾数=0x0。https://www.h-schmidt.net/FloatConverter/IEEE754.html有一个很好的交互式小数<->用于单精度的位模式转换器,带有用于设置位的复选框以及尾数和指数表示的注释。

在Skylake上,我在快速检查中发现的唯一快速情况是甚至2次方,比如4.0,但不是2.0。这些数字具有精确的sqrt结果,输入和输出都具有1.0尾数(仅隐式1位集)9.0并不快,即使它是可精确表示的,3.0的结果也是如此3.0具有尾数=1.5,并且仅在二进制表示中设置尾数的最高有效位。9.0的尾数是1.125(0b00100…)。所以非零位非常接近顶部,但显然这足以取消它的资格。

(+-InfNaN也很快。普通负数也很快:result=-NaN。我在i7-6700k上测量了它们的13个周期延迟,与4.0相同。而慢速情况下为18个周期延迟。)

CCD_ 7与CCD_。它有一个简单的输入和简单的输出。

对于2.0,输入也很简单(尾数为零,指数为1以上),但输出不是整数。sqrt(2)是无理的,因此在任何基中都有无限个非零位。这显然让Skylake的速度变慢了。

Agner Fog的指令表表示,AMD K10的整数div指令性能取决于被除数(输入)中的有效位数,而不是商,但搜索Agner的微阵列pdf和指令表没有找到任何关于sqrt具体如何依赖数据的脚注或信息。

在FP sqrt更慢的旧CPU上,可能有更大的速度范围。我认为输入尾数中的有效位数可能是相关的。如果正确的话,更少的有效位(有效位中更多的尾随零)会使它更快。但同样,在Haswell/Skylake上,唯一快速的案例似乎是2的偶数幂。


您可以用将输出耦合回输入而不破坏数据依赖性的东西来测试此,例如andps xmm0, xmm1/orps xmm0, xmm2,以在xmm0中设置一个依赖于sqrtsd输出的固定值。

或者测试延迟的一种更简单的方法是"利用"sqrtsd xmm0, xmm1的错误输出依赖性——它和sqrtss(分别)不修改目标的高64/32位,因此输出寄存器也是合并的输入我想这就是为什么你天真的内联asm尝试最终会导致延迟而不是吞吐量的瓶颈编译器为输出选择了一个不同的寄存器,这样它就可以在循环中重新读取相同的输入。您在问题中添加的内联asm完全损坏,甚至无法编译,但您的实际代码可能使用了"x"(xmm寄存器)输入和输出约束,而不是"i"(立即)?

静态可执行测试循环(在perf stat下运行)的这个NASM源使用了sqrtsd的非VEX编码的错误依赖关系。

这种ISA设计的优势要归功于英特尔在奔腾III上使用SSE1进行的短期优化。P3将128位寄存器内部处理为两个64位的两半。不修改上半部分,让标量指令解码为单个uop。(但这仍然给PIIIsqrtss带来了错误的依赖性)。AVX最后让我们避免了这一点,vsqrtsd dst, src,src至少适用于寄存器源,类似地vcvtsi2sd dst, cold_reg, eax适用于设计类似的标量int->fp转换指令。(GCC错过优化报告:8058689071和80571。)


在许多早期的CPU上,即使吞吐量也是可变的,但Skylake对分频器进行了足够的增强,使调度器始终知道它可以在最后一个单精度输入后的3个周期启动新的div/sqrt-uop。

即使是Skylake的双精度吞吐量也是可变的:如果Agner Fog的指令表是正确的,那么在最后一个双精度输入uop之后的4到6个周期。https://uops.info/显示出平坦的6c倒数吞吐量。(对于256位矢量,长度是256位矢量的两倍;128位和标量可以使用宽SIMD除法器的单独一半,以获得更高的吞吐量但具有相同的延迟。)有关从Agner Fog指令表中提取的一些吞吐量/延迟数,请参阅浮点除法与浮点乘法。

最新更新