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

C 为什么sqrtsd指令的延迟会根据输入而改变?英特尔处理器,c,performance,assembly,intel,cpu-architecture,C,Performance,Assembly,Intel,Cpu Architecture,实际上,有人说,名为“sqrtsd”的指令有18个周期的延迟 我用自己的程序测试了它,例如,如果我们将0.15作为输入,它是正确的。但当我们取256(或任意2^x)时,延迟只有13。为什么呢 我的一个理论是,由于13是“sqrtss”的延迟,它与“sqrtsd”相同,但是在32位浮点上完成的,那么处理器可能足够聪明,可以理解taht 256可以适合32位,因此使用该版本,而0.15需要完整的64位,因为它不能以有限的方式表示 我使用内联汇编来完成这项工作,下面是使用gcc-O3和-fno树矢量化

实际上,有人说,名为“sqrtsd”的指令有18个周期的延迟

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

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

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

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中非规范aka次正常FP值的微码辅助)。其他一切几乎都是固定不变的,因此无序的uop调度机制不需要等待确认某个周期的结果是否准备就绪,它只知道它将准备就绪

与往常一样,英特尔的《intrinsics指南》给出了一幅过于简化的性能图。在Skylake上,双精度的实际延迟不是固定的18个周期。(根据您选择引用的数字,我假设您有一个Skylake。)

div/sqrt难以实施;即使在硬件中,我们也能做的最好的事情就是迭代优化过程。一次细化更多位(Broadwell以来的基数1024除法器)可以加快速度(请参阅)但它的速度仍然足够慢,可以使用“早出”来加速简单的情况(或者,加速机制只是跳过了现代CPU上所有零尾数的设置步骤,这些CPU具有部分管道化的div/sqrt单元。较旧的CPU的吞吐量=FP div/sqrt的延迟;该执行单元更难管道化。)


显示Skylake SQRTSD的延迟周期从13到19个不等。SKL(客户机)编号仅显示13个周期的延迟,但我们可以从详细信息中看出,它们仅在输入=0时进行测试。SKX(服务器)编号显示13-19周期延迟。(有他们使用的测试代码的详细分类,包括测试的二进制位模式。)类似的测试(对于客户端核心只有0)在第页完成:/

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

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

是什么让一些案例快速? 请注意,大多数ISA(包括x86)使用:
这些位将值表示为线性有效位(也称尾数)乘以2exp和符号位

现代英特尔似乎只有两种速度(至少从哈斯韦尔开始)(见评论中与@harold的讨论)。例如,即使是2的幂也都很快,比如0.25、1、4和16。它们具有表示1.0的平凡尾数=0x0。有一个很好的交互式十进制位模式转换器,用于单精度,带有设置位的复选框以及尾数和指数表示的注释

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

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

x=sqrt(x)
对于
x=1.0
(除隐式前导1位之外的所有零尾数)来说绝对是快的。它有一个简单的输入和输出

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

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

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


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

或一种更简单的测试延迟的方法是“利用”sqrtsd xmm0、xmm1的假输出依赖性,并保持目标的高64/32位(分别)不变,因此输出寄存器也是该合并的输入我想这就是您天真的内联asm尝试的方式