SSE 和 AVX 中没有 float/double 的插入和提取?
No insert and extract for float/double in SSE and AVX?
我刚刚注意到缺少 _mm256_insert_pd()
/_mm256_insert_ps()
/_mm_insert_pd()
,_mm_insert_ps() 也存在,但使用模式有些奇怪。
同时存在 _mm_insert_epi32() and _mm256_insert_epi32() 和其他整数变体。
英特尔是否出于某种原因有意不实施 float/double 变体?在 SSE/AVX 寄存器的给定位置(不仅是第 0 个)设置单个 float/double 的正确且性能最好的方法是什么?
我实现了 insert
的 AVX-double 变体,它有效,但也许还有更好的方法来做到这一点:
template <int I>
__m256d _mm256_insert_pd(__m256d a, double x) {
int64_t ix;
std::memcpy(&ix, &x, sizeof(x));
return _mm256_castsi256_pd(
_mm256_insert_epi64(_mm256_castpd_si256(a), ix, I)
);
}
正如我所见,extract
float/double 由于某种原因在 SSE/AVX 中也没有变体。我只知道 _mm_extract_ps() 存在,其他的不存在。
你知道为什么 float/double SSE/AVX 没有 insert
和 extract
吗?
标量 float/double 已经是 XMM/YMM 寄存器的底部元素,并且有各种 FP 洗牌指令,包括 vinsertps
和 vmovlhps
可以(在asm) 插入 32 位或 64 位元素。但是,没有适用于 256 位 YMM 寄存器的版本,并且一般的 2 寄存器洗牌直到 AVX-512 才可用,并且只能使用矢量控制。
仍然有很多困难在于内在函数 API,这使得获得有用的 asm 操作变得更加困难。
一个不错的方法是广播标量 float 或 double 并混合,部分原因是广播是内在函数已经提供的获取包含标量 __m256d
的方法之一 1.
立即混合指令可以有效地替换另一个向量的一个元素,即使在高半部分2。它们在大多数 AVX CPU 上具有良好的吞吐量和延迟以及后端端口分布。它们需要 SSE4.1,但使用 AVX 它们始终可用。
(另请参阅 Agner Fog 的 VectorClass Library (VCL),用于替换矢量元素的 C++ 模板;具有各种 SSE/AVX 功能级别。包括运行时变量索引,但通常旨在优化到好的东西对于编译时常量,例如 Vec4f::insert()
)
中的索引开关
float
变成 __m256
template <int pos>
__m256 insert_float(__m256 v, float x) {
__m256 xv = _mm256_set1_ps(x);
return _mm256_blend_ps(v, xv, 1<<pos);
}
最好的情况是位置=0。 (Godbolt)
auto test2_merge_0(__m256 v, float x){
return insert_float<0>(v,x);
}
clang 注意到广播是多余的并对其进行了优化:
test2_merge_0(float __vector(8), float):
vblendps ymm0, ymm0, ymm1, 1 # ymm0 = ymm1[0],ymm0[1,2,3,4,5,6,7]
ret
但是 clang 有时会变得过于聪明而不利于自身利益,并将其悲观化为
test2_merge_5(float __vector(8), float): # clang(trunk) -O3 -march=skylake
vextractf128 xmm2, ymm0, 1
vinsertps xmm1, xmm2, xmm1, 16 # xmm1 = xmm2[0],xmm1[0],xmm2[2,3]
vinsertf128 ymm0, ymm0, xmm1, 1
ret
或者合并到归零向量时,clang 使用 vxorps
-归零然后混合,但 gcc 做得更好:
test2_zero_0(float): # GCC(trunk) -O3 -march=skylake
vinsertps xmm0, xmm0, xmm0, 0xe
ret
脚注 1:
这对内在函数来说是个问题;您可以与标量 float/double 一起使用的许多内在函数仅适用于向量操作数,并且编译器并不总是设法优化掉 _mm_set_ss
或 _mm_set1_ps
或任何当您只实际阅读底部元素。标量 float/double 要么在内存中,要么已经在 X/YMM 寄存器的底部元素中,因此在 asm 中,可以 100% 自由地对已经加载到寄存器中的标量浮点数/双精度数使用向量随机播放。
但是没有内在函数告诉编译器你想要一个底部外有无关元素的向量。这意味着您必须以一种看起来像是在做额外工作的方式编写源代码,并依靠编译器对其进行优化。 How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics?
脚注 2:
不像 vpinsrq
。正如您从 Godbolt 中看到的那样,您的版本编译效率非常低,尤其是使用 GCC 时。他们必须单独处理 __m256d
的高半部分,尽管 GCC 找到了更少的优化方式并使 asm 更接近您非常低效的代码。顺便说一句,使函数 return
成为 __m256d
而不是分配给 volatile
;这样你就有更少的噪音。 https://godbolt.org/z/Wrn7n4soh)
_mm256_insert_epi64
是一个“复合”内在/辅助函数:vpinsrq
仅以 vpinsrq xmm, xmm, r/m64, imm8
形式可用,它将 xmm 寄存器零扩展为完整的 Y/ZMM.即使是 clang 的 shuffle 优化器(发现 vmovlhps
以用另一个 XMM 的低半部分替换 XMM 的高半部分)当您混合到现有向量而不是零时,最终仍然会提取并重新插入高半部分。
asm 情况是 extractps
的标量操作数是 r/m32
,而不是 XMM 寄存器,因此它对提取标量浮点数没有用(除了将其存储到内存中)。有关更多信息,请参阅 my answer on the Q&A Intel SSE: Why does `_mm_extract_ps` return `int` instead of `float`? 和 insertps
。
insertps xmm, xmm/m32, imm
可以 select 来自另一个向量寄存器的源浮点数,所以唯一的内在函数需要两个向量,给你留下 How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics? 当你只关心底部的元素时,说服编译器不要浪费指令设置元素的问题 __m128
。
我刚刚注意到缺少 _mm256_insert_pd()
/_mm256_insert_ps()
/_mm_insert_pd()
,_mm_insert_ps() 也存在,但使用模式有些奇怪。
同时存在 _mm_insert_epi32() and _mm256_insert_epi32() 和其他整数变体。
英特尔是否出于某种原因有意不实施 float/double 变体?在 SSE/AVX 寄存器的给定位置(不仅是第 0 个)设置单个 float/double 的正确且性能最好的方法是什么?
我实现了 insert
的 AVX-double 变体,它有效,但也许还有更好的方法来做到这一点:
template <int I>
__m256d _mm256_insert_pd(__m256d a, double x) {
int64_t ix;
std::memcpy(&ix, &x, sizeof(x));
return _mm256_castsi256_pd(
_mm256_insert_epi64(_mm256_castpd_si256(a), ix, I)
);
}
正如我所见,extract
float/double 由于某种原因在 SSE/AVX 中也没有变体。我只知道 _mm_extract_ps() 存在,其他的不存在。
你知道为什么 float/double SSE/AVX 没有 insert
和 extract
吗?
标量 float/double 已经是 XMM/YMM 寄存器的底部元素,并且有各种 FP 洗牌指令,包括 vinsertps
和 vmovlhps
可以(在asm) 插入 32 位或 64 位元素。但是,没有适用于 256 位 YMM 寄存器的版本,并且一般的 2 寄存器洗牌直到 AVX-512 才可用,并且只能使用矢量控制。
仍然有很多困难在于内在函数 API,这使得获得有用的 asm 操作变得更加困难。
一个不错的方法是广播标量 float 或 double 并混合,部分原因是广播是内在函数已经提供的获取包含标量 __m256d
的方法之一 1.
立即混合指令可以有效地替换另一个向量的一个元素,即使在高半部分2。它们在大多数 AVX CPU 上具有良好的吞吐量和延迟以及后端端口分布。它们需要 SSE4.1,但使用 AVX 它们始终可用。
(另请参阅 Agner Fog 的 VectorClass Library (VCL),用于替换矢量元素的 C++ 模板;具有各种 SSE/AVX 功能级别。包括运行时变量索引,但通常旨在优化到好的东西对于编译时常量,例如 Vec4f::insert()
)
float
变成 __m256
template <int pos>
__m256 insert_float(__m256 v, float x) {
__m256 xv = _mm256_set1_ps(x);
return _mm256_blend_ps(v, xv, 1<<pos);
}
最好的情况是位置=0。 (Godbolt)
auto test2_merge_0(__m256 v, float x){
return insert_float<0>(v,x);
}
clang 注意到广播是多余的并对其进行了优化:
test2_merge_0(float __vector(8), float):
vblendps ymm0, ymm0, ymm1, 1 # ymm0 = ymm1[0],ymm0[1,2,3,4,5,6,7]
ret
但是 clang 有时会变得过于聪明而不利于自身利益,并将其悲观化为
test2_merge_5(float __vector(8), float): # clang(trunk) -O3 -march=skylake
vextractf128 xmm2, ymm0, 1
vinsertps xmm1, xmm2, xmm1, 16 # xmm1 = xmm2[0],xmm1[0],xmm2[2,3]
vinsertf128 ymm0, ymm0, xmm1, 1
ret
或者合并到归零向量时,clang 使用 vxorps
-归零然后混合,但 gcc 做得更好:
test2_zero_0(float): # GCC(trunk) -O3 -march=skylake
vinsertps xmm0, xmm0, xmm0, 0xe
ret
脚注 1:
这对内在函数来说是个问题;您可以与标量 float/double 一起使用的许多内在函数仅适用于向量操作数,并且编译器并不总是设法优化掉 _mm_set_ss
或 _mm_set1_ps
或任何当您只实际阅读底部元素。标量 float/double 要么在内存中,要么已经在 X/YMM 寄存器的底部元素中,因此在 asm 中,可以 100% 自由地对已经加载到寄存器中的标量浮点数/双精度数使用向量随机播放。
但是没有内在函数告诉编译器你想要一个底部外有无关元素的向量。这意味着您必须以一种看起来像是在做额外工作的方式编写源代码,并依靠编译器对其进行优化。 How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics?
脚注 2:
不像 vpinsrq
。正如您从 Godbolt 中看到的那样,您的版本编译效率非常低,尤其是使用 GCC 时。他们必须单独处理 __m256d
的高半部分,尽管 GCC 找到了更少的优化方式并使 asm 更接近您非常低效的代码。顺便说一句,使函数 return
成为 __m256d
而不是分配给 volatile
;这样你就有更少的噪音。 https://godbolt.org/z/Wrn7n4soh)
_mm256_insert_epi64
是一个“复合”内在/辅助函数:vpinsrq
仅以 vpinsrq xmm, xmm, r/m64, imm8
形式可用,它将 xmm 寄存器零扩展为完整的 Y/ZMM.即使是 clang 的 shuffle 优化器(发现 vmovlhps
以用另一个 XMM 的低半部分替换 XMM 的高半部分)当您混合到现有向量而不是零时,最终仍然会提取并重新插入高半部分。
asm 情况是 extractps
的标量操作数是 r/m32
,而不是 XMM 寄存器,因此它对提取标量浮点数没有用(除了将其存储到内存中)。有关更多信息,请参阅 my answer on the Q&A Intel SSE: Why does `_mm_extract_ps` return `int` instead of `float`? 和 insertps
。
insertps xmm, xmm/m32, imm
可以 select 来自另一个向量寄存器的源浮点数,所以唯一的内在函数需要两个向量,给你留下 How to merge a scalar into a vector without the compiler wasting an instruction zeroing upper elements? Design limitation in Intel's intrinsics? 当你只关心底部的元素时,说服编译器不要浪费指令设置元素的问题 __m128
。