是否可以在 AVX/SSE 中得到多个正弦?
Is it possible to get multiple sines in AVX/SSE?
我正在尝试编写一个 C++ 程序,它启动我在 x64 汇编器中编写的函数。
我想加快速度(并使用 CPU 功能),所以我选择使用矢量运算。
问题是,我必须将正弦乘以一个整数,所以我必须先计算正弦。
是否可以在 SSE/AVX 中执行此操作?我知道指令 fsin
,但它不仅在 FPU 中,而且一次只计算 1 个正弦。所以我必须将它推入 FPU,调用 fsin
,将其从 FPU 弹出到内存,然后将其放入 AVX 寄存器。在我看来,这不值得麻烦。
SSE/AVX 中没有正弦指令,但是根据您需要的精度,您可以使用 Taylor/Madhava series or as the quotient of two polynomials using Pade Approximant 将正弦函数的近似值写为多项式。当然还有更多的多项式逼近技术。
这是否会产生您想要的精度以及此方法的速度取决于您的具体问题。一般来说,多项式逼近非常快,因为可以使用 n 个 FMA 指令(Pade 逼近也需要一次除法)通过将其写成
的形式来评估 n 次多项式
a+x*(b+x*(c+x*(...))).
然而,正弦在使用多项式进行近似时表现出了众所周知的不良行为,因此用例受到限制。
是的,有一个矢量版本使用SSE/AVX! 但问题是必须使用 Intel C++ 编译器。
这叫做 Intel 小型矢量数学库(内在函数):
对于 128 位 SSE,请使用(双精度):_mm_sin_pd
对于 256 位 AVX,请使用(双精度):_mm256_sin_pd
这两个内部函数实际上是非常小的函数,由手写的 SSE/AVX 程序集组成,现在您可以使用 AVX 一次处理 4 个正弦计算 :=) 延迟约为 10 个时钟周期(如果我没记错)在 Haswell CPU。
顺便说一下,CPU 需要执行大约 100 个这样的内部函数来预热并达到其最佳性能,如果只需要评估几个 sin 函数,最好使用普通 sin () 反而。
祝你好运!!
由于 OpenMP 4.0 需要矢量化 sin/cos 扩展,gcc-glibc 也在 libmvec
中提供了它们。参见:
- 用于声明函数原型的头文件;
- https://sourceware.org/glibc/wiki/libmvec 了解 libmvec 是什么以及您通常如何使用它。
有关其他 SVML 备选方案的列表,请参阅 。
样本余弦近似:
- 0.15 ULPS 平均值(1 ULPS 最大值)误差
- AVX512 加速 12 倍,AVX2 加速 8-9 倍
[-1,1] 范围内余弦的近似值。
多项式系数是通过遗传算法找到的。乘法级数看起来像 Chebyshev 多项式,但它们不是,但是当您需要更广泛的输入范围而不仅仅是 [-1,1].
时需要它们
// only optimized for [-1,1] input range!!
template<typename Type, int Simd>
inline
void cosFast(
Type * const __restrict__ data,
Type * const __restrict__ result) noexcept
{
alignas(64)
Type xSqr[Simd];
alignas(64)
Type xSqrSqr[Simd];
alignas(64)
Type xSqrSqrSqr[Simd];
alignas(64)
Type xSqrSqrSqrSqr[Simd];
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
xSqr[i] = data[i]*data[i];
}
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
xSqrSqr[i] = xSqr[i]*xSqr[i];
}
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
xSqrSqrSqr[i] = xSqrSqr[i]*xSqr[i];
}
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
xSqrSqrSqrSqr[i] = xSqrSqr[i]*xSqrSqr[i];
}
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
result[i] = Type(2.37711074060342753000441e-05)*xSqrSqrSqrSqr[i] +
Type(-0.001387712893937020908197155)*xSqrSqrSqr[i] +
Type(0.04166611039514833692010143)*xSqrSqr[i] +
Type(-0.4999998698566363586337502)*xSqr[i] +
Type(0.9999999941252593060880827);
}
}
如果你需要使用更宽的范围,那么你应该在高精度下做一个range-reduction并使用这样的东西:
- 范围缩小到 -pi,pi 高精度
- 除以“4”使其在-1,1范围内
- 计算与上面相同的系列 => tmp
- 计算(切比雪夫)L_"4" (tmp) = 结果
.L29:
vmovups ymm7, YMMWORD PTR [r14+rax]
vmulps ymm1, ymm7, ymm7
vmovups ymm7, YMMWORD PTR [r14+32+rax]
vmulps ymm3, ymm1, ymm1
vmulps ymm6, ymm3, ymm3
vmulps ymm6, ymm6, YMMWORD PTR .LC11[rip]
vmulps ymm0, ymm7, ymm7
vmulps ymm5, ymm3, ymm1
vfmadd132ps ymm5, ymm6, YMMWORD PTR .LC12[rip]
vmulps ymm2, ymm0, ymm0
vmulps ymm6, ymm2, ymm2
vmulps ymm6, ymm6, YMMWORD PTR .LC11[rip]
vfmadd132ps ymm3, ymm5, YMMWORD PTR .LC13[rip]
vmulps ymm4, ymm2, ymm0
vfmadd132ps ymm4, ymm6, YMMWORD PTR .LC12[rip]
vfmadd132ps ymm1, ymm3, YMMWORD PTR .LC14[rip]
vfmadd132ps ymm2, ymm4, YMMWORD PTR .LC13[rip]
vaddps ymm1, ymm1, YMMWORD PTR .LC15[rip]
vfmadd132ps ymm0, ymm2, YMMWORD PTR .LC14[rip]
vaddps ymm0, ymm0, YMMWORD PTR .LC15[rip]
vmovups YMMWORD PTR [r15+rax], ymm1
vmovups YMMWORD PTR [r15+32+rax], ymm0
add rax, 64
cmp rax, 16777216
jne .L29
我正在尝试编写一个 C++ 程序,它启动我在 x64 汇编器中编写的函数。 我想加快速度(并使用 CPU 功能),所以我选择使用矢量运算。
问题是,我必须将正弦乘以一个整数,所以我必须先计算正弦。
是否可以在 SSE/AVX 中执行此操作?我知道指令 fsin
,但它不仅在 FPU 中,而且一次只计算 1 个正弦。所以我必须将它推入 FPU,调用 fsin
,将其从 FPU 弹出到内存,然后将其放入 AVX 寄存器。在我看来,这不值得麻烦。
SSE/AVX 中没有正弦指令,但是根据您需要的精度,您可以使用 Taylor/Madhava series or as the quotient of two polynomials using Pade Approximant 将正弦函数的近似值写为多项式。当然还有更多的多项式逼近技术。
这是否会产生您想要的精度以及此方法的速度取决于您的具体问题。一般来说,多项式逼近非常快,因为可以使用 n 个 FMA 指令(Pade 逼近也需要一次除法)通过将其写成
的形式来评估 n 次多项式a+x*(b+x*(c+x*(...))).
然而,正弦在使用多项式进行近似时表现出了众所周知的不良行为,因此用例受到限制。
是的,有一个矢量版本使用SSE/AVX! 但问题是必须使用 Intel C++ 编译器。
这叫做 Intel 小型矢量数学库(内在函数):
对于 128 位 SSE,请使用(双精度):_mm_sin_pd
对于 256 位 AVX,请使用(双精度):_mm256_sin_pd
这两个内部函数实际上是非常小的函数,由手写的 SSE/AVX 程序集组成,现在您可以使用 AVX 一次处理 4 个正弦计算 :=) 延迟约为 10 个时钟周期(如果我没记错)在 Haswell CPU。
顺便说一下,CPU 需要执行大约 100 个这样的内部函数来预热并达到其最佳性能,如果只需要评估几个 sin 函数,最好使用普通 sin () 反而。
祝你好运!!
由于 OpenMP 4.0 需要矢量化 sin/cos 扩展,gcc-glibc 也在 libmvec
中提供了它们。参见:
- 用于声明函数原型的头文件;
- https://sourceware.org/glibc/wiki/libmvec 了解 libmvec 是什么以及您通常如何使用它。
有关其他 SVML 备选方案的列表,请参阅
样本余弦近似:
- 0.15 ULPS 平均值(1 ULPS 最大值)误差
- AVX512 加速 12 倍,AVX2 加速 8-9 倍 [-1,1] 范围内余弦的近似值。
多项式系数是通过遗传算法找到的。乘法级数看起来像 Chebyshev 多项式,但它们不是,但是当您需要更广泛的输入范围而不仅仅是 [-1,1].
时需要它们 // only optimized for [-1,1] input range!!
template<typename Type, int Simd>
inline
void cosFast(
Type * const __restrict__ data,
Type * const __restrict__ result) noexcept
{
alignas(64)
Type xSqr[Simd];
alignas(64)
Type xSqrSqr[Simd];
alignas(64)
Type xSqrSqrSqr[Simd];
alignas(64)
Type xSqrSqrSqrSqr[Simd];
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
xSqr[i] = data[i]*data[i];
}
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
xSqrSqr[i] = xSqr[i]*xSqr[i];
}
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
xSqrSqrSqr[i] = xSqrSqr[i]*xSqr[i];
}
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
xSqrSqrSqrSqr[i] = xSqrSqr[i]*xSqrSqr[i];
}
#pragma GCC ivdep
for(int i=0;i<Simd;i++)
{
result[i] = Type(2.37711074060342753000441e-05)*xSqrSqrSqrSqr[i] +
Type(-0.001387712893937020908197155)*xSqrSqrSqr[i] +
Type(0.04166611039514833692010143)*xSqrSqr[i] +
Type(-0.4999998698566363586337502)*xSqr[i] +
Type(0.9999999941252593060880827);
}
}
如果你需要使用更宽的范围,那么你应该在高精度下做一个range-reduction并使用这样的东西:
- 范围缩小到 -pi,pi 高精度
- 除以“4”使其在-1,1范围内
- 计算与上面相同的系列 => tmp
- 计算(切比雪夫)L_"4" (tmp) = 结果
.L29:
vmovups ymm7, YMMWORD PTR [r14+rax]
vmulps ymm1, ymm7, ymm7
vmovups ymm7, YMMWORD PTR [r14+32+rax]
vmulps ymm3, ymm1, ymm1
vmulps ymm6, ymm3, ymm3
vmulps ymm6, ymm6, YMMWORD PTR .LC11[rip]
vmulps ymm0, ymm7, ymm7
vmulps ymm5, ymm3, ymm1
vfmadd132ps ymm5, ymm6, YMMWORD PTR .LC12[rip]
vmulps ymm2, ymm0, ymm0
vmulps ymm6, ymm2, ymm2
vmulps ymm6, ymm6, YMMWORD PTR .LC11[rip]
vfmadd132ps ymm3, ymm5, YMMWORD PTR .LC13[rip]
vmulps ymm4, ymm2, ymm0
vfmadd132ps ymm4, ymm6, YMMWORD PTR .LC12[rip]
vfmadd132ps ymm1, ymm3, YMMWORD PTR .LC14[rip]
vfmadd132ps ymm2, ymm4, YMMWORD PTR .LC13[rip]
vaddps ymm1, ymm1, YMMWORD PTR .LC15[rip]
vfmadd132ps ymm0, ymm2, YMMWORD PTR .LC14[rip]
vaddps ymm0, ymm0, YMMWORD PTR .LC15[rip]
vmovups YMMWORD PTR [r15+rax], ymm1
vmovups YMMWORD PTR [r15+32+rax], ymm0
add rax, 64
cmp rax, 16777216
jne .L29