是否有一个内部函数可以将 __m128i 向量的最后 n 个字节归零?
Is there an intrinsic function to zero out the last n bytes of a __m128i vector?
给定 n
,我想将 __m128i
向量的最后 n
个字节清零。
例如考虑以下 __m128i
向量:
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
将最后 n = 4
个字节归零后,矢量应如下所示:
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 00000000 00000000 00000000 00000000
是否有 SSE 内部函数可以执行此操作(通过接受 __128i
向量和 n
作为参数)?
您应该能够通过使用 _mm_mask_set1_epi8
intrinsic:
将零“广播”到向量末尾的所需字节来实现所需的结果
__m128i _mm_mask_set1_epi8 (__m128i src, __mmask16 k, char a)
src
是您的 __m128i
矢量
__mmask16
由 n
构造为 (1 << n) - 1
,即最后有 n
个的掩码
char a
为零
有多种不依赖于 AVX512 的选项。例如:
未对齐的负载
char mask[32] = { 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1};
__m128i zeroLowestNBytes(__m128i x, uint32_t n)
{
__m128i m = _mm_loadu_si128((__m128i*)&mask[16 - n]);
return _mm_and_si128(x, m);
}
借助 AVX,加载可以成为 vpand
的内存操作数。没有 AVX 它仍然很好,有 movdqu
和 pand
.
负载未对齐通常不是问题,除非它跨越 4K 边界。如果你能得到 mask
32 对齐,那么这个问题就会消失。负载仍未对齐,但不会达到特定的边缘情况。
n
是一个 uint32_t
来避免 sign-extension.
广播和比较
__m128i zeroLowestNBytes(__m128i x, int n)
{
__m128i threshold = _mm_set1_epi8(n);
__m128i index = _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
return _mm_andnot_si128(_mm_cmpgt_epi8(threshold, index), x);
}
这避免了未对齐的加载,但这并不重要。更重要的是,它避免了“input-dependent负载”:在具有未对齐负载的版本中,负载取决于n
。在这个版本中,负载独立于n
。例如,如果此函数是内联的,则允许编译器将其提升到循环之外。它还允许 out-of-order 执行更自由地尽早开始加载,也许在 n
计算之前。
不利的一面是,它基本上需要 AVX2 或 SSSE3 才能像样地实现 _mm_set1_epi8(n)
。此外,这通常会花费更多的指令,这可能会降低吞吐量。延迟应该更好,因为“主链”中没有负载(有负载,但它在一边,它不会将其延迟添加到计算延迟中)。
给定 n
,我想将 __m128i
向量的最后 n
个字节清零。
例如考虑以下 __m128i
向量:
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
将最后 n = 4
个字节归零后,矢量应如下所示:
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 00000000 00000000 00000000 00000000
是否有 SSE 内部函数可以执行此操作(通过接受 __128i
向量和 n
作为参数)?
您应该能够通过使用 _mm_mask_set1_epi8
intrinsic:
__m128i _mm_mask_set1_epi8 (__m128i src, __mmask16 k, char a)
src
是您的__m128i
矢量__mmask16
由n
构造为(1 << n) - 1
,即最后有n
个的掩码char a
为零
有多种不依赖于 AVX512 的选项。例如:
未对齐的负载
char mask[32] = { 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1};
__m128i zeroLowestNBytes(__m128i x, uint32_t n)
{
__m128i m = _mm_loadu_si128((__m128i*)&mask[16 - n]);
return _mm_and_si128(x, m);
}
借助 AVX,加载可以成为 vpand
的内存操作数。没有 AVX 它仍然很好,有 movdqu
和 pand
.
负载未对齐通常不是问题,除非它跨越 4K 边界。如果你能得到 mask
32 对齐,那么这个问题就会消失。负载仍未对齐,但不会达到特定的边缘情况。
n
是一个 uint32_t
来避免 sign-extension.
广播和比较
__m128i zeroLowestNBytes(__m128i x, int n)
{
__m128i threshold = _mm_set1_epi8(n);
__m128i index = _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
return _mm_andnot_si128(_mm_cmpgt_epi8(threshold, index), x);
}
这避免了未对齐的加载,但这并不重要。更重要的是,它避免了“input-dependent负载”:在具有未对齐负载的版本中,负载取决于n
。在这个版本中,负载独立于n
。例如,如果此函数是内联的,则允许编译器将其提升到循环之外。它还允许 out-of-order 执行更自由地尽早开始加载,也许在 n
计算之前。
不利的一面是,它基本上需要 AVX2 或 SSSE3 才能像样地实现 _mm_set1_epi8(n)
。此外,这通常会花费更多的指令,这可能会降低吞吐量。延迟应该更好,因为“主链”中没有负载(有负载,但它在一边,它不会将其延迟添加到计算延迟中)。