英特尔矢量指令将 32 位 int 中打包的 8 个 4 位值零扩展为 __m256i?
Intel vector instruction to zero-extend 8 4-bit values packed in a 32-bit int to a __m256i?
如问题所述,我有一个正常的 int,它是 8 个打包值,每个值 4 位,我想将其零扩展到 256 位向量寄存器中。 sse/avx/avx2 可以吗?
这是一个应该保持顺序的解决方案:
__m256i foo(int x) {
__m128i input = _mm_cvtsi32_si128(x);
__m128i even = input;
// move odd nibbles to even positions:
__m128i odd = _mm_srli_epi32(input,4);
// interleave: (only lower 64bit are used)
__m128i inter = _mm_unpacklo_epi8(even, odd);
// mask out wrong nibbles:
__m128i masked = _mm_and_si128(inter, _mm_set1_epi32(0x0f0f0f0f));
// convert to 32bit:
return _mm256_cvtepu8_epi32(masked);
}
神箭link:https://godbolt.org/z/8RLUVE
如果您一次加载两个或四个 int32
以交错和屏蔽偶数和奇数半字节,您的效率可能会稍高一些。 (当然,这会导致多个 __m256i
向量)
中的(余数中称为cvt_nib_epi32_chtz
)很合适
用于一般用途。但是,在某些特定情况下,下面介绍的解决方案可能
效率稍微高一点:
/* gcc -O3 -m64 -Wall -march=skylake cvt_nib_epi32.c */
#include <immintrin.h>
#include <stdio.h>
#include <stdint.h>
__m256i cvt_nib_epi32_SKL(uint32_t x) { /* Efficient on Intel Skylake and newer */
/* Broadcast x to 8 elements */
__m256i input = _mm256_set1_epi32(x);
/* Shift the nibbles to the right position */
__m256i shifted = _mm256_srlv_epi32(input,_mm256_set_epi32(28,24,20,16,12,8,4,0));
/* Mask off the unwanted bits and return */
return _mm256_and_si256(shifted, _mm256_set1_epi32(0xF));
}
__m256i cvt_nib_epi32_HSW(uint32_t x) { /* Efficient on intel Haswell and Broadwell */
/* Very inefficient in AMD Zen! */
__uint64_t x_b = _pdep_u64(x, 0x0F0F0F0F0F0F0F0F); /* Expand nibbles to bytes */
__m128i x_v = _mm_cvtsi64_si128(x_b); /* Move x_b from GPR to AVX vector register */
return _mm256_cvtepu8_epi32(x_v); /* Convert bytes to integer elements and return */
}
下面的程序集是generated by gcc:
cvt_nib_epi32_SKL:
vmovd xmm0, edi
vpbroadcastd ymm0, xmm0
vpsrlvd ymm0, ymm0, YMMWORD PTR .LC0[rip]
vpand ymm0, ymm0, YMMWORD PTR .LC1[rip]
ret
cvt_nib_epi32_HSW:
movabs rax, 1085102592571150095
mov edi, edi
pdep rdi, rdi, rax
vmovq xmm0, rdi
vpmovzxbd ymm0, xmm0
ret
cvt_nib_epi32_chtz:
vmovd xmm0, edi
vpsrld xmm1, xmm0, 4
vpunpcklbw xmm0, xmm0, xmm1
vpand xmm0, xmm0, XMMWORD PTR .LC2[rip]
vpmovzxbd ymm0, xmm0
ret
函数cvt_nib_epi32_chtz
非常适合AMD zen微架构,
因为它不使用指令 pdep
和 vpsrlvd
,这些指令在这些处理器上速度很慢。
在 Intel 处理器上,cvt_nib_epi32_chtz
可能会受到影响
来自高端口 5 (p5) 的压力,取决于周围的代码,
因为 vmovd
、vpunpcklbw
和 vpmovzxbd
都在 p5 上执行。
其他函数仅解码为 2 p5 微指令。
Skylake 解决方案 cvt_nib_epi32_SKL
使用 vpsrlvd
,速度较慢
在 Intel Haswell 和 Broadwell 上。
对于这些处理器 cvt_nib_epi32_HSW
是合适的。它使用 BMI2 指令 pdep
,它非常(!)慢
AMD 禅宗微架构。请注意 cvt_nib_epi32_HSW
也应该在 Intel Skylake 上运行良好,但是
(再次)实际性能取决于周围的代码。
请注意,在循环上下文中不断加载,例如YMMWORD PTR .LC0[rip]
和movabs rax, 1085102592571150095
,
很可能被提升到循环之外。在这种情况下,只需要 4 微指令
cvt_nib_epi32_HSW
和 cvt_nib_epi32_SKL
.
如问题所述,我有一个正常的 int,它是 8 个打包值,每个值 4 位,我想将其零扩展到 256 位向量寄存器中。 sse/avx/avx2 可以吗?
这是一个应该保持顺序的解决方案:
__m256i foo(int x) {
__m128i input = _mm_cvtsi32_si128(x);
__m128i even = input;
// move odd nibbles to even positions:
__m128i odd = _mm_srli_epi32(input,4);
// interleave: (only lower 64bit are used)
__m128i inter = _mm_unpacklo_epi8(even, odd);
// mask out wrong nibbles:
__m128i masked = _mm_and_si128(inter, _mm_set1_epi32(0x0f0f0f0f));
// convert to 32bit:
return _mm256_cvtepu8_epi32(masked);
}
神箭link:https://godbolt.org/z/8RLUVE
如果您一次加载两个或四个 int32
以交错和屏蔽偶数和奇数半字节,您的效率可能会稍高一些。 (当然,这会导致多个 __m256i
向量)
中的cvt_nib_epi32_chtz
)很合适
用于一般用途。但是,在某些特定情况下,下面介绍的解决方案可能
效率稍微高一点:
/* gcc -O3 -m64 -Wall -march=skylake cvt_nib_epi32.c */
#include <immintrin.h>
#include <stdio.h>
#include <stdint.h>
__m256i cvt_nib_epi32_SKL(uint32_t x) { /* Efficient on Intel Skylake and newer */
/* Broadcast x to 8 elements */
__m256i input = _mm256_set1_epi32(x);
/* Shift the nibbles to the right position */
__m256i shifted = _mm256_srlv_epi32(input,_mm256_set_epi32(28,24,20,16,12,8,4,0));
/* Mask off the unwanted bits and return */
return _mm256_and_si256(shifted, _mm256_set1_epi32(0xF));
}
__m256i cvt_nib_epi32_HSW(uint32_t x) { /* Efficient on intel Haswell and Broadwell */
/* Very inefficient in AMD Zen! */
__uint64_t x_b = _pdep_u64(x, 0x0F0F0F0F0F0F0F0F); /* Expand nibbles to bytes */
__m128i x_v = _mm_cvtsi64_si128(x_b); /* Move x_b from GPR to AVX vector register */
return _mm256_cvtepu8_epi32(x_v); /* Convert bytes to integer elements and return */
}
下面的程序集是generated by gcc:
cvt_nib_epi32_SKL:
vmovd xmm0, edi
vpbroadcastd ymm0, xmm0
vpsrlvd ymm0, ymm0, YMMWORD PTR .LC0[rip]
vpand ymm0, ymm0, YMMWORD PTR .LC1[rip]
ret
cvt_nib_epi32_HSW:
movabs rax, 1085102592571150095
mov edi, edi
pdep rdi, rdi, rax
vmovq xmm0, rdi
vpmovzxbd ymm0, xmm0
ret
cvt_nib_epi32_chtz:
vmovd xmm0, edi
vpsrld xmm1, xmm0, 4
vpunpcklbw xmm0, xmm0, xmm1
vpand xmm0, xmm0, XMMWORD PTR .LC2[rip]
vpmovzxbd ymm0, xmm0
ret
函数cvt_nib_epi32_chtz
非常适合AMD zen微架构,
因为它不使用指令 pdep
和 vpsrlvd
,这些指令在这些处理器上速度很慢。
在 Intel 处理器上,cvt_nib_epi32_chtz
可能会受到影响
来自高端口 5 (p5) 的压力,取决于周围的代码,
因为 vmovd
、vpunpcklbw
和 vpmovzxbd
都在 p5 上执行。
其他函数仅解码为 2 p5 微指令。
Skylake 解决方案 cvt_nib_epi32_SKL
使用 vpsrlvd
,速度较慢
在 Intel Haswell 和 Broadwell 上。
对于这些处理器 cvt_nib_epi32_HSW
是合适的。它使用 BMI2 指令 pdep
,它非常(!)慢
AMD 禅宗微架构。请注意 cvt_nib_epi32_HSW
也应该在 Intel Skylake 上运行良好,但是
(再次)实际性能取决于周围的代码。
请注意,在循环上下文中不断加载,例如YMMWORD PTR .LC0[rip]
和movabs rax, 1085102592571150095
,
很可能被提升到循环之外。在这种情况下,只需要 4 微指令
cvt_nib_epi32_HSW
和 cvt_nib_epi32_SKL
.