Tom*_*y95 10 c performance assembly intel cpu-architecture
好吧,在Intel 的内在指南中指出,名为“sqrtsd”的指令的延迟为 18 个周期。
我用自己的程序对其进行了测试,例如,如果我们将 0.15 作为输入,则它是正确的。但是当我们取 256(或任何 2^x)个数字时,延迟只有 13。这是为什么呢?
我的一个理论是,由于 13 是“sqrtss”的延迟,它与“sqrtsd”相同,但在 32 位浮点上完成,那么也许处理器足够聪明,可以理解 256 可以适应 32 位,因此使用该版本而 0.15 需要完整的 64 位,因为它不能以有限的方式表示。
我正在使用内联汇编来完成它,这是使用 gcc -O3 和 -fno-tree-vectorize 编译的相关部分。
static double sqrtsd (double x) {
double r;
__asm__ ("sqrtsd %1, %0" : "=x" (r) : "x" (x));
return r;
}
Run Code Online (Sandbox Code Playgroud)
Pet*_*des 11
SQRT* 和 DIV* 是仅有的两个在现代 Intel/AMD CPU 上具有依赖于数据的吞吐量或延迟的“简单”ALU 指令(单 uop,而不是微编码分支/循环)。(不计算对加/乘/fma 中的非正规即次正规 FP 值的微码辅助)。其他一切都非常固定,因此无序的 uop 调度机制不需要等待确认结果已准备好某个周期,它只知道它会准备好。
像往常一样,英特尔的内在指南给出了一个过于简化的性能图。Skylake 上双精度的实际延迟不是固定的 18 个周期。(根据您选择引用的数字,我假设您有一个 Skylake。)
div/sqrt 很难实现;即使在硬件方面,我们能做的最好的事情也是迭代细化过程。一次精炼更多位(自 Broadwell 以来的 radix-1024 分频器)可加快速度(请参阅有关硬件的此问答)。 但是它仍然足够慢,以至于使用提前退出来加速简单的情况 (或者加速机制可能只是跳过具有部分流水线 div/sqrt 单元的现代 CPU 上全零尾数的设置步骤。较旧的 CPU 具有吞吐量= FP div/sqrt 的延迟;该执行单元更难以流水线化。)
https://www.uops.info/html-instr/VSQRTSD_XMM_XMM_XMM.html显示 Skylake SQRTSD 可以从 13 到 19 个周期延迟不等。SKL(客户端)编号仅显示 13 个周期的延迟,但我们可以从详细的SKL vsqrtsd 页面中看到,他们仅在输入 = 0 时进行了测试。SKX(服务器)编号显示了 13-19 个周期的延迟。(这个页面有他们使用的测试代码的详细分类,包括测试的二进制位模式。)在非 VEXsqrtsd xmm, xmm页面上进行了类似的测试(客户端内核只有 0)。:/
InstLatx64结果显示 Skylake-X(使用与 Skylake-client 相同的核心,但启用了 AVX512)上的最佳/最坏情况延迟为 13 到 18 个周期。
Agner Fog 的指令表显示了 Skylake 上 15-16 个周期的延迟。(Agner 通常使用一系列不同的输入值进行测试。)他的测试自动化程度较低,有时与其他结果不完全匹配。
请注意,大多数 ISA(包括 x86)使用二进制浮点数:
这些位将值表示为线性有效数(又名尾数)乘以 2 exp和一个符号位。
似乎现代英特尔上可能只有 2 种速度(至少从 Haswell 开始) (请参阅评论中与 @harold 的讨论。)例如,即使是 2 的幂都很快,例如 0.25、1、4 和 16。这些都是微不足道的尾数=0x0 代表 1.0。 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...)。所以非零位非常接近顶部,但显然这足以取消它的资格。
(+-Inf而且NaN速度也很快。普通的负数也是如此: result = -NaN。我在 i7-6700k 上测量了 13 个周期的延迟,与4.0. 与慢速情况下的 18 个周期延迟相同。)
x = sqrt(x)绝对快x = 1.0(除隐式前导 1 位外,尾数全为零)。它具有简单的输入和简单的输出。
在 2.0 中,输入也很简单(尾数全为零,指数高 1),但输出不是整数。sqrt(2) 是无理数,因此在任何基数中都有无限的非零位。这显然使它在 Skylake 上运行缓慢。
Agner Fog 的指令表说 AMD K10 的整数div指令性能取决于被除数(输入)中的有效位数,而不是商,但是搜索 Agner 的 microarch pdf 和指令表没有找到任何脚注或有关 sqrt 具体如何的信息数据依赖。
在 FP sqrt 更慢的旧 CPU 上,可能有更多的速度范围空间。我认为输入尾数中的有效位数可能是相关的。如果这是正确的,更少的有效位(有效数中更多的尾随零)使其更快。但同样,在 Haswell/Skylake 上,唯一的快速案例似乎是 2 的偶次幂。
您可以使用将输出耦合回输入而不破坏数据依赖性的东西来测试它,例如andps xmm0, xmm1/orps xmm0, xmm2在依赖于 sqrtsd 输出的 xmm0 中设置一个固定值。
或者测试延迟的一种更简单的方法是利用sqrtsd xmm0, xmm1- it的错误输出依赖性的“优势”,并保持目标sqrtss的高 64 / 32 位(分别)保持不变,因此输出寄存器也是该合并的输入。 我认为这就是您天真的 inline-asm 尝试最终导致延迟瓶颈而不是吞吐量瓶颈的原因,编译器为输出选择不同的寄存器,以便它可以在循环中重新读取相同的输入。您添加到问题中的内联 asm 已完全损坏,甚至无法编译,但也许您的真实代码使用了"x"(xmm 寄存器)输入和输出约束而不是"i"(立即)?
用于静态可执行测试循环(在 下运行perf stat)的NASM 源使用该错误依赖项和sqrtsd.
这个 ISA 设计问题要归功于 Intel 在 Pentium III 上使用 SSE1 进行短期优化。P3 在内部将 128 位寄存器处理为两个 64 位半。保持上半部分不变,让标量指令解码为单个 uop。(但这仍然给 PIIIsqrtss一个错误的依赖)。AVX 最终让我们vsqrtsd dst, src,src至少对于寄存器源避免了这种情况,vcvtsi2sd dst, cold_reg, eax对于类似的近视设计的标量 int->fp 转换指令也是如此。(GCC 未命中优化报告:80586、89071、80571。)
在许多早期的 CPU 上,甚至吞吐量是可变的,但 Skylake 加强了分频器,以便调度程序始终知道它可以在最后一个单精度输入后的 3 个周期开始一个新的 div/sqrt uop。
但是,即使是 Skylake 双精度吞吐量也是可变的:如果Agner Fog 的指令表是正确的,那么在最后一个双精度输入uop之后的 4 到 6 个周期。 https://uops.info/显示平坦的 6c 互惠吞吐量。(或者 256 位向量的两倍长;128 位和标量可以使用宽 SIMD 分频器的单独一半以获得更高的吞吐量,但具有相同的延迟。)另请参阅浮点除法与浮点乘法以了解提取的某些吞吐量/延迟数字来自 Agner Fog 的指令表。