将 int64_t 移动到 AVX2 __m256i 向量的高位四字
Move an int64_t to the high quadwords of an AVX2 __m256i vector
此题与[1]类似。但是我不太明白它是如何解决使用 GPR 插入 ymm 的高位四字的问题。此外,我希望该操作不使用任何中间内存访问。
可以用AVX2或更低版本来完成吗(我没有AVX512)?
[1]
我的回答 没有说明这样做的方法,因为如果没有用于屏蔽广播的 AVX512F (vpbroadcastq zmm0{k1}, rax
),它就无法非常有效地完成。但实际上使用暂存器并没有那么糟糕,与 vpinsrq
+ 立即混合的成本大致相同。
(在 Intel 上,总共 3 微指令。端口 5(vmovq + 广播)2 微指令,以及可以在任何端口上 运行 的即时混合。
参见 https://agner.org/optimize/)。
我为此用 asm 更新了我的答案。在具有 Intel 内在函数的 C++ 中,您可以执行以下操作:
#include <immintrin.h>
#include <stdint.h>
// integer version. An FP version would still use _mm256_set1_epi64x, then a cast
template<unsigned elem>
static inline
__m256i merge_epi64(__m256i v, int64_t newval)
{
static_assert(elem <= 3, "a __m256i only has 4 qword elements");
__m256i splat = _mm256_set1_epi64x(newval);
constexpr unsigned dword_blendmask = 0b11 << (elem*2); // vpblendd uses 2 bits per qword
return _mm256_blend_epi32(v, splat, dword_blendmask);
}
Clang 对所有 4 个可能的元素位置几乎完美地进行了编译,这真正展示了它的随机播放优化器是多么的好。它利用了所有的特殊情况。作为奖励,它注释了它的 asm 以向您展示哪些元素来自混合和随机播放的位置。
From the Godbolt compiler explorer,一些测试函数,看看 regs 中的 args 会发生什么。
__m256i merge3(__m256i v, int64_t newval) {
return merge_epi64<3> (v, newval);
}
// and so on for 2..0
# clang7.0 -O3 -march=haswell
merge3(long long __vector(4), long):
vmovq xmm1, rdi
vpbroadcastq ymm1, xmm1
vpblendd ymm0, ymm0, ymm1, 192 # ymm0 = ymm0[0,1,2,3,4,5],ymm1[6,7]
# 192 = 0xC0 = 0b11000000
ret
merge2(long long __vector(4), long):
vmovq xmm1, rdi
vinserti128 ymm1, ymm0, xmm1, 1 # Runs on more ports than vbroadcast on AMD Ryzen
# But it introduced a dependency on v (ymm0) before the blend for no reason, for the low half of ymm1. Could have used xmm1, xmm1.
vpblendd ymm0, ymm0, ymm1, 48 # ymm0 = ymm0[0,1,2,3],ymm1[4,5],ymm0[6,7]
ret
merge1(long long __vector(4), long):
vmovq xmm1, rdi
vpbroadcastq xmm1, xmm1 # only an *XMM* broadcast, 1c latency instead of 3.
vpblendd ymm0, ymm0, ymm1, 12 # ymm0 = ymm0[0,1],ymm1[2,3],ymm0[4,5,6,7]
ret
merge0(long long __vector(4), long):
vmovq xmm1, rdi
# broadcast optimized away, newval is already in the low element
vpblendd ymm0, ymm0, ymm1, 3 # ymm0 = ymm1[0,1],ymm0[2,3,4,5,6,7]
ret
其他编译器盲目广播到完整的 YMM,然后混合,即使 elem=0。 您可以专门化模板,或在模板中添加 if()
条件以进行优化。 例如splat = (elem?) set1() : v;
为 elem==0 保存广播。如果需要,您也可以捕获其他优化。
GCC 8.x 和更早版本使用一种通常很糟糕的整数广播方式:它们 store/reload。这避免了使用任何 ALU 洗牌端口,因为广播负载在 Intel CPU 上是免费的,但它在从整数到最终向量结果的链中引入了存储转发延迟。
这在 gcc9 的当前 t运行k 中已修复,但我不知道是否有解决方法可以使用较早的 gcc 获得非愚蠢的代码生成。通常 -march=<an intel uarch>
支持 ALU 而不是 store/reload 用于整数 -> 向量,反之亦然,但在这种情况下,成本模型仍然选择 store/reload 和 -march=haswell
.
# gcc8.2 -O3 -march=haswell
merge0(long long __vector(4), long):
push rbp
mov rbp, rsp
and rsp, -32 # align the stack even though no YMM is spilled/loaded
mov QWORD PTR [rsp-8], rdi
vpbroadcastq ymm1, QWORD PTR [rsp-8] # 1 uop on Intel
vpblendd ymm0, ymm0, ymm1, 3
leave
ret
; GCC trunk: g++ (GCC-Explorer-Build) 9.0.0 20190103 (experimental)
; MSVC and ICC do this, too. (For MSVC, make sure to compile with -arch:AVX2)
merge0(long long __vector(4), long):
vmovq xmm2, rdi
vpbroadcastq ymm1, xmm2
vpblendd ymm0, ymm0, ymm1, 3
ret
对于运行随时间变化的元素位置,随机播放仍然有效,但您必须创建一个混合掩码向量,并在正确的元素。例如从 alignas(8) int8_t mask[] = { 0,0,0,-1,0,0,0 };
中的 mask[3-elem]
加载 vpmovsxbq
。但是 vpblendvb
或 vblendvpd
比立即混合要慢,尤其是在 Haswell 上,因此请尽可能避免。
此题与[1]类似。但是我不太明白它是如何解决使用 GPR 插入 ymm 的高位四字的问题。此外,我希望该操作不使用任何中间内存访问。
可以用AVX2或更低版本来完成吗(我没有AVX512)?
[1]
我的回答 vpbroadcastq zmm0{k1}, rax
),它就无法非常有效地完成。但实际上使用暂存器并没有那么糟糕,与 vpinsrq
+ 立即混合的成本大致相同。
(在 Intel 上,总共 3 微指令。端口 5(vmovq + 广播)2 微指令,以及可以在任何端口上 运行 的即时混合。 参见 https://agner.org/optimize/)。
我为此用 asm 更新了我的答案。在具有 Intel 内在函数的 C++ 中,您可以执行以下操作:
#include <immintrin.h>
#include <stdint.h>
// integer version. An FP version would still use _mm256_set1_epi64x, then a cast
template<unsigned elem>
static inline
__m256i merge_epi64(__m256i v, int64_t newval)
{
static_assert(elem <= 3, "a __m256i only has 4 qword elements");
__m256i splat = _mm256_set1_epi64x(newval);
constexpr unsigned dword_blendmask = 0b11 << (elem*2); // vpblendd uses 2 bits per qword
return _mm256_blend_epi32(v, splat, dword_blendmask);
}
Clang 对所有 4 个可能的元素位置几乎完美地进行了编译,这真正展示了它的随机播放优化器是多么的好。它利用了所有的特殊情况。作为奖励,它注释了它的 asm 以向您展示哪些元素来自混合和随机播放的位置。
From the Godbolt compiler explorer,一些测试函数,看看 regs 中的 args 会发生什么。
__m256i merge3(__m256i v, int64_t newval) {
return merge_epi64<3> (v, newval);
}
// and so on for 2..0
# clang7.0 -O3 -march=haswell
merge3(long long __vector(4), long):
vmovq xmm1, rdi
vpbroadcastq ymm1, xmm1
vpblendd ymm0, ymm0, ymm1, 192 # ymm0 = ymm0[0,1,2,3,4,5],ymm1[6,7]
# 192 = 0xC0 = 0b11000000
ret
merge2(long long __vector(4), long):
vmovq xmm1, rdi
vinserti128 ymm1, ymm0, xmm1, 1 # Runs on more ports than vbroadcast on AMD Ryzen
# But it introduced a dependency on v (ymm0) before the blend for no reason, for the low half of ymm1. Could have used xmm1, xmm1.
vpblendd ymm0, ymm0, ymm1, 48 # ymm0 = ymm0[0,1,2,3],ymm1[4,5],ymm0[6,7]
ret
merge1(long long __vector(4), long):
vmovq xmm1, rdi
vpbroadcastq xmm1, xmm1 # only an *XMM* broadcast, 1c latency instead of 3.
vpblendd ymm0, ymm0, ymm1, 12 # ymm0 = ymm0[0,1],ymm1[2,3],ymm0[4,5,6,7]
ret
merge0(long long __vector(4), long):
vmovq xmm1, rdi
# broadcast optimized away, newval is already in the low element
vpblendd ymm0, ymm0, ymm1, 3 # ymm0 = ymm1[0,1],ymm0[2,3,4,5,6,7]
ret
其他编译器盲目广播到完整的 YMM,然后混合,即使 elem=0。 您可以专门化模板,或在模板中添加 if()
条件以进行优化。 例如splat = (elem?) set1() : v;
为 elem==0 保存广播。如果需要,您也可以捕获其他优化。
GCC 8.x 和更早版本使用一种通常很糟糕的整数广播方式:它们 store/reload。这避免了使用任何 ALU 洗牌端口,因为广播负载在 Intel CPU 上是免费的,但它在从整数到最终向量结果的链中引入了存储转发延迟。
这在 gcc9 的当前 t运行k 中已修复,但我不知道是否有解决方法可以使用较早的 gcc 获得非愚蠢的代码生成。通常 -march=<an intel uarch>
支持 ALU 而不是 store/reload 用于整数 -> 向量,反之亦然,但在这种情况下,成本模型仍然选择 store/reload 和 -march=haswell
.
# gcc8.2 -O3 -march=haswell
merge0(long long __vector(4), long):
push rbp
mov rbp, rsp
and rsp, -32 # align the stack even though no YMM is spilled/loaded
mov QWORD PTR [rsp-8], rdi
vpbroadcastq ymm1, QWORD PTR [rsp-8] # 1 uop on Intel
vpblendd ymm0, ymm0, ymm1, 3
leave
ret
; GCC trunk: g++ (GCC-Explorer-Build) 9.0.0 20190103 (experimental)
; MSVC and ICC do this, too. (For MSVC, make sure to compile with -arch:AVX2)
merge0(long long __vector(4), long):
vmovq xmm2, rdi
vpbroadcastq ymm1, xmm2
vpblendd ymm0, ymm0, ymm1, 3
ret
对于运行随时间变化的元素位置,随机播放仍然有效,但您必须创建一个混合掩码向量,并在正确的元素。例如从 alignas(8) int8_t mask[] = { 0,0,0,-1,0,0,0 };
中的 mask[3-elem]
加载 vpmovsxbq
。但是 vpblendvb
或 vblendvpd
比立即混合要慢,尤其是在 Haswell 上,因此请尽可能避免。