有没有办法水平对AVX寄存器进行异或,特别是对256位寄存器的4个64位组件进行异或?
目标是获得AVX寄存器的所有4个64位组件的XOR。它基本上与水平加法(_mm256_hadd_epi32()
)做同样的事情,除了我想要XOR而不是ADD。
标量代码是:
inline uint64_t HorizontalXor(__m256i t) {
return t.m256i_u64[0] ^ t.m256i_u64[1] ^ t.m256i_u64[2] ^ t.m256i_u64[3];
}
答案 0 :(得分:10)
如评论中所述,最快的代码很可能使用标量运算,在整数寄存器中执行所有操作。你需要做的就是提取四个打包的64位整数,然后你有三个XOR
指令,你就完成了。这可以非常有效地完成,并将结果留在整数寄存器中,这是您的示例代码建议您想要的。
MSVC已经为您在问题中显示的标量函数生成了相当不错的代码:
inline uint64_t HorizontalXor(__m256i t) {
return t.m256i_u64[0] ^ t.m256i_u64[1] ^ t.m256i_u64[2] ^ t.m256i_u64[3];
}
假设t
位于ymm1
,生成的反汇编将是这样的:
vextractf128 xmm0, ymm1, 1
vpextrq rax, xmm0, 1
vmovq rcx, xmm1
xor rax, rcx
vpextrq rcx, xmm1, 1
vextractf128 xmm0, ymm1, 1
xor rax, rcx
vmovq rcx, xmm0
xor rax, rcx
...结果保留在RAX
中。如果这准确反映了您的需求(标量uint64_t
结果),则此代码就足够了。
您可以使用内在函数稍微改进它:
inline uint64_t _mm256_hxor_epu64(__m256i x)
{
const __m128i temp = _mm256_extracti128_si256(x, 1);
return (uint64_t&)x
^ (uint64_t)(_mm_extract_epi64(_mm256_castsi256_si128(x), 1))
^ (uint64_t&)(temp)
^ (uint64_t)(_mm_extract_epi64(temp, 1));
}
然后您将获得以下反汇编(同样,假设x
位于ymm1
):
vextracti128 xmm2, ymm1, 1
vpextrq rcx, xmm2, 1
vpextrq rax, xmm1, 1
xor rax, rcx
vmovq rcx, xmm1
xor rax, rcx
vmovq rcx, xmm2
xor rax, rcx
请注意,我们能够忽略一条提取指令,并确保使用VEXTRACTI128
代替VEXTRACTF128
(尽管this choice probably does not matter)。
您将在其他编译器上看到类似的输出。例如,这里是GCC 7.1(x
假设为ymm0
):
vextracti128 xmm2, ymm0, 0x1
vpextrq rax, xmm0, 1
vmovq rdx, xmm2
vpextrq rcx, xmm2, 1
xor rax, rdx
vmovq rdx, xmm0
xor rax, rdx
xor rax, rcx
有相同的说明,但它们已经稍微重新排序了。内在函数允许编译器的调度程序按其认为最佳的顺序进行排序。 Clang 4.0以不同的方式安排它们:
vmovq rax, xmm0
vpextrq rcx, xmm0, 1
xor rcx, rax
vextracti128 xmm0, ymm0, 1
vmovq rdx, xmm0
xor rdx, rcx
vpextrq rax, xmm0, 1
xor rax, rdx
当然,当代码内联时,这种排序总是会发生变化。
另一方面,如果您希望结果在AVX寄存器中,那么您首先需要决定如何存储它。我想你只会将单个64位结果存储为标量,如:
inline __m256i _mm256_hxor(__m256i x)
{
const __m128i temp = _mm256_extracti128_si256(x, 1);
return _mm256_set1_epi64x((uint64_t&)x
^ (uint64_t)(_mm_extract_epi64(_mm256_castsi256_si128(x), 1))
^ (uint64_t&)(temp)
^ (uint64_t)(_mm_extract_epi64(temp, 1)));
}
但是现在你正在进行大量的数据改组,否定了从矢量化代码中可能看到的任何性能提升。
说到这一点,我不确定你是如何让自己陷入这样一种情况,你需要首先进行这样的横向操作。 SIMD操作旨在垂直缩放 ,而不是水平缩放。如果您仍处于实施阶段,则可能需要重新考虑设计。特别是,您应该在4个不同的 AVX寄存器中生成4个整数值,而不是将它们全部打包成一个。
如果您确实希望将 4份的结果打包到AVX寄存器中,那么您可以这样做:
inline __m256i _mm256_hxor(__m256i x)
{
const __m256i temp = _mm256_xor_si256(x,
_mm256_permute2f128_si256(x, x, 1));
return _mm256_xor_si256(temp,
_mm256_shuffle_epi32(temp, _MM_SHUFFLE(1, 0, 3, 2)));
}
这仍然通过一次执行两次XOR来利用一点并行性,这意味着只需要两次XOR操作,而不是三次。
如果它有助于可视化,这基本上可以:
A B C D ⟵ input
XOR XOR XOR XOR
C D A B ⟵ permuted input
=====================================
A^C B^D A^C B^D ⟵ intermediate result
XOR XOR XOR XOR
B^D A^C B^D A^C ⟵ shuffled intermediate result
======================================
A^C^B^D A^C^B^D A^C^B^D A^C^B^D ⟵ final result
在几乎所有编译器上,这些内在函数将生成以下汇编代码:
vperm2f128 ymm0, ymm1, ymm1, 1 ; input is in YMM1
vpxor ymm2, ymm0, ymm1
vpshufd ymm1, ymm2, 78
vpxor ymm0, ymm1, ymm2
(我在第一次发布这个答案后的路上想出了这个,并计划回来更新答案,但我看到wim在发布它时打败了我。好吧,它仍然比我最初的方法更好,所以它仍然值得包含在这里。)
当然,如果你想在整数寄存器中使用它,你只需要一个简单的VMOVQ
:
vperm2f128 ymm0, ymm1, ymm1, 1 ; input is in YMM1
vpxor ymm2, ymm0, ymm1
vpshufd ymm1, ymm2, 78
vpxor ymm0, ymm1, ymm2
vmovq rax, xmm0
问题是,这会比上面的标量代码更快。答案是,是的,可能。虽然您使用AVX执行单元进行XOR,而不是完全独立的整数执行单元,但需要完成的AVX shuffles / permutes / extract更少,这意味着开销更少。因此,我可能还需要在标量代码上吃掉我的话,这是最快的实现。但这实际上取决于你正在做什么以及如何安排/交错指令。
答案 1 :(得分:4)
如果水平xor
- 函数的输入已经存在,则矢量化可能很有用
一个AVX寄存器,即你的t
是一些SIMD计算的结果。
否则,标量代码可能会更快,正如@Cody Gray已经提到的那样。
通常,您可以在log_2(SIMD_width)'步骤'中进行水平SIMD操作。
在这种情况下,一步是“洗牌/置换”。和' xor'。这比@Cody Gray的_mm256_hxor
函数稍微有效:
__m256i _mm256_hxor_v2(__m256i x)
{
__m256i x0 = _mm256_permute2x128_si256(x,x,1); // swap the 128 bit high and low lane
__m256i x1 = _mm256_xor_si256(x,x0);
__m256i x2 = _mm256_shuffle_epi32(x1,0b01001110); // swap 64 bit lanes
__m256i x3 = _mm256_xor_si256(x1,x2);
return x3;
}
这编译为:
vperm2i128 $1, %ymm0, %ymm0, %ymm1
vpxor %ymm1, %ymm0, %ymm0
vpshufd $78, %ymm0, %ymm1
vpxor %ymm1, %ymm0, %ymm0
如果您希望将结果存入标量寄存器:
uint64_t _mm256_hxor_v2_uint64(__m256i x)
{
__m256i x0 = _mm256_permute2x128_si256(x,x,1);
__m256i x1 = _mm256_xor_si256(x,x0);
__m256i x2 = _mm256_shuffle_epi32(x1,0b01001110);
__m256i x3 = _mm256_xor_si256(x1,x2);
return _mm_cvtsi128_si64x(_mm256_castsi256_si128(x3)) ;
}
编译为:
vperm2i128 $1, %ymm0, %ymm0, %ymm1
vpxor %ymm1, %ymm0, %ymm0
vpshufd $78, %ymm0, %ymm1
vpxor %ymm1, %ymm0, %ymm0
vmovq %xmm0, %rax
完整的测试代码:
#include <stdio.h>
#include <x86intrin.h>
#include <stdint.h>
/* gcc -O3 -Wall -m64 -march=broadwell hor_xor.c */
int print_vec_uint64(__m256i v);
__m256i _mm256_hxor_v2(__m256i x)
{
__m256i x0 = _mm256_permute2x128_si256(x,x,1);
__m256i x1 = _mm256_xor_si256(x,x0);
__m256i x2 = _mm256_shuffle_epi32(x1,0b01001110);
__m256i x3 = _mm256_xor_si256(x1,x2);
/* Uncomment the next few lines to print the values of the intermediate variables */
/*
printf("3...0 = 3 2 1 0\n");
printf("x = ");print_vec_uint64(x );
printf("x0 = ");print_vec_uint64(x0 );
printf("x1 = ");print_vec_uint64(x1 );
printf("x2 = ");print_vec_uint64(x2 );
printf("x3 = ");print_vec_uint64(x3 );
*/
return x3;
}
uint64_t _mm256_hxor_v2_uint64(__m256i x)
{
__m256i x0 = _mm256_permute2x128_si256(x,x,1);
__m256i x1 = _mm256_xor_si256(x,x0);
__m256i x2 = _mm256_shuffle_epi32(x1,0b01001110);
__m256i x3 = _mm256_xor_si256(x1,x2);
return _mm_cvtsi128_si64x(_mm256_castsi256_si128(x3)) ;
}
int main() {
__m256i x = _mm256_set_epi64x(0x7, 0x5, 0x2, 0xB);
// __m256i x = _mm256_set_epi64x(4235566778345231, 1123312566778345423, 72345566778345673, 967856775433457);
printf("x = ");print_vec_uint64(x);
__m256i y = _mm256_hxor_v2(x);
printf("y = ");print_vec_uint64(y);
uint64_t z = _mm256_hxor_v2_uint64(x);
printf("z = %10lX \n",z);
return 0;
}
int print_vec_uint64(__m256i v){
uint64_t t[4];
_mm256_storeu_si256((__m256i *)t,v);
printf("%10lX %10lX %10lX %10lX \n",t[3],t[2],t[1],t[0]);
return 0;
}
答案 2 :(得分:2)
对于XOR,_mm256_hadd_epi32()
的直接模拟的实现将如下所示:
#include <immintrin.h>
template<int imm> inline __m256i _mm256_shuffle_epi32(__m256i a, __m256i b)
{
return _mm256_castps_si256(_mm256_shuffle_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b), imm));
}
inline __m256i _mm256_hxor_epi32(__m256i a, __m256i b)
{
return _mm256_xor_si256(_mm256_shuffle_epi32<0x88>(a, b), _mm256_shuffle_epi32<0xDD>(a, b));
}
int main()
{
__m256i a = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
__m256i b = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 8);
__m256i c = _mm256_hxor_epi32(a, b);
return 0;
}