是否有任何常见的 fixed-point 内在函数?

Are there any common fixed-point intrinsics?

我要让标题更笼统,但我特别想要一个 快速 64 位平方根 (sqrt) 函数用于输入之间 0.5 和 1.0。 (实际上,一些“SSE2 reciprocal sqrt”会是 非常适合我的数值模拟,但我认为这也是 很多要求。而且,为了完整,一个小的划分是 也在使用中。)

我遇到的内在函数使用 floating-point 数据 因此为指数浪费了 11 位。我知道 53 位是 几乎是 64 位,所以是的,CPU 制造商可能会假设人们 像我一样需要在软件中设计一些 bigint 算法 无论如何,所以我猜 CPU 制造商只是把这个放在他们的低位 优先列表。

或者,是否有更大的理由避免 fixed-point 内在函数 我失踪了吗?如果我需要比 53 位稍好一点的 准确度(例如 60 位准确度),我是否只需要接受一个 ~10x slow-down?

标题问题:_mm_mulhrs_epi16 (pmulhrsw) 我认为是用于 16 位 fixed-point 乘以平均。

sqrt:x86 不支持任何整数平方根,但 x87 fsqrt 指令确实可以处理带有 64 位尾数的 80 位 long double。 (即 C 中的 sqrtl())。但是一次只有一个(标量),吞吐量比 sqrtpd 还要差。这也将花费 store/reload 延迟获取 x87 寄存器的数据 in/out,即使 fild / fistp 可以将 from/to int64_t 转换为 round-to-nearest,如果你能让 C 编译器发出那些。

例如在 GNU/Linux(或其他 non-Windows 平台,其中 long double 是 80 位 x87 类型),我认为这可能是可行的:

#include <stdint.h>
#include <math.h>

int64_t fixed_point_sqrt(int64_t a) {
    return lrintl(sqrtl(a) * (1LL<<32));  // rescale for your fixed point range
}

(lrintl 使用当前默认舍入模式进行 long double -> long 转换,即舍入到最近。否则只是强制转换,你可以获得 SSE3 fisttp 截断,或者没有SSE3 将舍入模式缓慢更改为截断并返回。)

使用 GCC 和 clang(针对 Linux),您可以(在 Godbolt 上)

# gcc and clang -O3 -fno-math-errno   are both similar; this is clang:
fixed_point_sqrt(long):                  # @fixed_point_sqrt(long)
        mov     qword ptr [rsp - 16], rdi
        fild    qword ptr [rsp - 16]         # convert int64 -> 80-bit x87
        fsqrt
        fmul    dword ptr [rip + .LCPI1_0]   # float 4.2949673E+9 is exactly representable
        fistp   qword ptr [rsp - 8]          # convert back with roundinging
        mov     rax, qword ptr [rsp - 8]
        ret

实际上,如果 80 位 FP 值始终是整数,那么 fisttp 与 fistp 可能无关紧要;不确定范围如何运作。


fixed-point 的正常 use-case 适用于您不想在指数字段上浪费 space 的窄元素;您的动态范围非常有限(最大值仅为最小值的两倍)的情况确实使指数字段对于 FP 值来说是多余的,但是现代 x86 CPU 在 SIMD-FP 吞吐量上花费了大量晶体管,因此这仍然是高性能的好选择.

如果您关心最大精度,请注意 64 位整数的整数平方根只有 32 位有效位。但是对于 fixed-point,0.5 和 1.0 之间的数字的平方根在 .75 和 1.0 之间,因此您只会损失 1 位精度(结果中始终设置 MSB)。所以重新缩放使其不同于纯整数 sqrt.


如果您需要更多的尾数位,您可以使用 double-double (https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format#Double-double_arithmetic) pairs of double, . Multiplying and adding SIMD 可以实现这样的数字,但我不知道如何有效地实现 sqrt。