是否有任何常见的 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
, double-double-arithmetic. Multiplying and adding SIMD 可以实现这样的数字,但我不知道如何有效地实现 sqrt。
我要让标题更笼统,但我特别想要一个 快速 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
, double-double-arithmetic. Multiplying and adding SIMD 可以实现这样的数字,但我不知道如何有效地实现 sqrt。