假设我有一个指向 RDI 中一堆
uint8_t
的指针,我想将 4 个 uint8_t
加载到 XMM0 中,并使用 SIMD 指令将其与 XMM1 相乘,其中我存储了 4 个 float
值。
如何将最初的 4 个
uint8_t
加载到 XMM0 中,使其始终“对齐”,这意味着每个“隔间”的低 8 位与 uint8_t
一起,高 24 位为 0?有相关说明吗?
我希望我的问题是可以理解的,对于我对我的问题的非常天真的解释感到抱歉。
movdqu xmm0, [rdi]
会导致加载 QWORD,而不是我需要的。
为了简单起见,我忽略了浮点乘法。我认为使用
mulps
并不是那么难。真正的挑战是转换。
对于SSE2,没有简单的操作。 英特尔内在函数实际上附带了一个内在函数,它可以为此扩展为一系列重要的操作。
#include <immintrin.h>
void u8ps_sse2(float* out, unsigned char* in)
{
__m64 in_low = _mm_cvtsi32_si64(*(const int*) in);
__m128 as_float = _mm_cvtpu8_ps(in_low);
_mm_storeu_ps(out, as_float);
}
u8ps_sse2(float*, unsigned char*):
movd xmm0, DWORD PTR [rsi]
pxor xmm1, xmm1
punpcklbw xmm0, xmm1
pxor xmm1, xmm1
movdqa xmm3, xmm0
punpcklwd xmm0, xmm1
punpcklwd xmm3, xmm1
pxor xmm1, xmm1
pshufd xmm0, xmm0, 0x4e
movaps xmm2, xmm1
cvtdq2ps xmm4, xmm3
cvtdq2ps xmm5, xmm0
shufps xmm1, xmm5, 0x4e
shufps xmm2, xmm4, 0x4e
pshufd xmm1, xmm1, 0x4e
pshufd xmm2, xmm2, 0x4e
movlhps xmm2, xmm1
movups XMMWORD PTR [rdi], xmm2
ret
SSSE3可以使用
pshufb
来实现零扩展。
void u8ps_ssse3(float* out, unsigned char* in)
{
__m128i in_low = _mm_cvtsi32_si128(*(const int*) in);
__m128i shuffle = _mm_set_epi8(
-1, -1, -1, 3,
-1, -1, -1, 2,
-1, -1, -1, 1,
-1, -1, -1, 0
);
__m128i zero_extended = _mm_shuffle_epi8(in_low, shuffle);
__m128 as_float = _mm_cvtepi32_ps(zero_extended);
_mm_storeu_ps(out, as_float);
}
u8ps_ssse3(float*, unsigned char*):
movd xmm0, DWORD PTR [rsi]
pshufb xmm0, XMMWORD PTR .LC0[rip]
cvtdq2ps xmm0, xmm0
movups XMMWORD PTR [rdi], xmm0
ret
.LC0:
.byte 0
.byte -1
.byte -1
.byte -1
.byte 1
.byte -1
.byte -1
.byte -1
.byte 2
.byte -1
.byte -1
.byte -1
.byte 3
.byte -1
.byte -1
.byte -1
最后,SSE4.1 给了我们正确的指示
pmovzxbd
。
void u8ps_sse41(float* out, unsigned char* in)
{
__m128i in_low = _mm_cvtsi32_si128(*(const int*) in);
__m128i zero_extended = _mm_cvtepu8_epi32(in_low);
__m128 as_float = _mm_cvtepi32_ps(zero_extended);
_mm_storeu_ps(out, as_float);
}
u8ps_sse41(float*, unsigned char*):
pmovzxbd xmm0, DWORD PTR [rsi]
cvtdq2ps xmm0, xmm0
movups XMMWORD PTR [rdi], xmm0
ret