为 `rsqrts` 包装器获取最少的指令
Getting Fewest Instructions for `rsqrtss` Wrapper
我认为是时候使用快速平方根倒数了。因此,我尝试编写一个函数(在生产中将被标记为 inline
):
float sqrt_recip(float x) {
return _mm_cvtss_f32( _mm_rsqrt_ss( _mm_set_ps1(x) ) ); //same as _mm_set1_ps
}
TL;DR:我的问题是 "how can I get GCC and ICC to output minimal assembly (two instructions) for the above function, preferably without resorting to raw assembly (sticking with intrinsics)?"
正如所写,在 ICC 13.0.1、GCC 5.2.0 和 Clang 3.7 上,输出是:
shufps xmm0, xmm0, 0
rsqrtss xmm0, xmm0
ret
这是有道理的,因为我使用 _mm_set_ps1
将 x
分散到寄存器的所有组件中。但是,我真的不需要那样做。我宁愿只做最后两行。当然,shufps
只是一个循环。但是 rsqrtss
只有三到五个。 20% 到 33% 的开销完全没有价值。
我试过的一些东西:
我试着不设置它:
union { __m128 v; float f[4]; } u;
u.f[0] = x;
return _mm_cvtss_f32(_mm_rsqrt_ss(u.v));
这实际上适用于 Clang,但 ICC 和 GCC 的输出尤其令人震惊。
您可以用零填充(即使用_mm_set_ss
)而不是分散。同样,GCC 和 ICC 的输出都不是最佳的。在 GCC 的例子中,GCC 搞笑地添加了这个:
movss DWORD PTR [rsp-12], xmm0
movss xmm0, DWORD PTR [rsp-12]
三年半过去了,虽然编译器进步了,情况也变好了,但仍然没有输出最优代码。
然而,在不下降到原始汇编的情况下,我们仍然可以通过使用内联汇编比内部函数做得更好。我们必须小心一点;在非 VEX 编码指令和 VEX 编码指令之间切换会有很大的损失,因此我们需要两个代码路径。
这会在 GCC (9.0.1)、Clang (9.0.0) 和 ICC (19.0.1.144) 上产生最佳结果。当内联而不是 VEX 编码时,它只会在 MSVC (19.16) 上产生最佳结果(这可能是我们能做的最好的,因为 MSVC 不支持 x86-64 上的内联汇编):
#include <xmmintrin.h>
inline float rsqrt_fast(float x) {
#ifndef _MSC_VER //Optimal
float result;
asm( //Note AT&T order
#ifdef __AVX__
"vrsqrtss %1, %1, %0"
#else
"rsqrtss %1, %0"
#endif
: "=x"(result)
: "x"(x)
);
return result;
#else //TODO: not optimal when in AVX mode or when not inlined
return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ps1(x)));
#endif
}
我认为是时候使用快速平方根倒数了。因此,我尝试编写一个函数(在生产中将被标记为 inline
):
float sqrt_recip(float x) {
return _mm_cvtss_f32( _mm_rsqrt_ss( _mm_set_ps1(x) ) ); //same as _mm_set1_ps
}
TL;DR:我的问题是 "how can I get GCC and ICC to output minimal assembly (two instructions) for the above function, preferably without resorting to raw assembly (sticking with intrinsics)?"
正如所写,在 ICC 13.0.1、GCC 5.2.0 和 Clang 3.7 上,输出是:
shufps xmm0, xmm0, 0
rsqrtss xmm0, xmm0
ret
这是有道理的,因为我使用 _mm_set_ps1
将 x
分散到寄存器的所有组件中。但是,我真的不需要那样做。我宁愿只做最后两行。当然,shufps
只是一个循环。但是 rsqrtss
只有三到五个。 20% 到 33% 的开销完全没有价值。
我试过的一些东西:
我试着不设置它:
union { __m128 v; float f[4]; } u;
u.f[0] = x;
return _mm_cvtss_f32(_mm_rsqrt_ss(u.v));
这实际上适用于 Clang,但 ICC 和 GCC 的输出尤其令人震惊。您可以用零填充(即使用
_mm_set_ss
)而不是分散。同样,GCC 和 ICC 的输出都不是最佳的。在 GCC 的例子中,GCC 搞笑地添加了这个:
movss DWORD PTR [rsp-12], xmm0
movss xmm0, DWORD PTR [rsp-12]
三年半过去了,虽然编译器进步了,情况也变好了,但仍然没有输出最优代码。
然而,在不下降到原始汇编的情况下,我们仍然可以通过使用内联汇编比内部函数做得更好。我们必须小心一点;在非 VEX 编码指令和 VEX 编码指令之间切换会有很大的损失,因此我们需要两个代码路径。
这会在 GCC (9.0.1)、Clang (9.0.0) 和 ICC (19.0.1.144) 上产生最佳结果。当内联而不是 VEX 编码时,它只会在 MSVC (19.16) 上产生最佳结果(这可能是我们能做的最好的,因为 MSVC 不支持 x86-64 上的内联汇编):
#include <xmmintrin.h>
inline float rsqrt_fast(float x) {
#ifndef _MSC_VER //Optimal
float result;
asm( //Note AT&T order
#ifdef __AVX__
"vrsqrtss %1, %1, %0"
#else
"rsqrtss %1, %0"
#endif
: "=x"(result)
: "x"(x)
);
return result;
#else //TODO: not optimal when in AVX mode or when not inlined
return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ps1(x)));
#endif
}