将 16 位值的 __m256i 打包(饱和)到 8 位值的 __m128i?

Posted

技术标签:

【中文标题】将 16 位值的 __m256i 打包(饱和)到 8 位值的 __m128i?【英文标题】:Pack (with saturation) __m256i of 16-bit values to __m128i of 8-bit values? 【发布时间】:2021-04-25 14:53:27 【问题描述】:

是否有 AVX 或 AVX2 操作可将 16x16 位无符号整数 (uint16_t) 值的 __m256i 转换为 16x8 位无符号整数 (uint8_t) 值的 __m128i采用饱和的低字节强>)?

_mm256_packus_epi16(),但它使用第一个输入的前 8 个字节,然后是第二个输入的前 8 个字节,然后是第一个和第二个输入的第二个 8 个字节......导致 8 个字节的组无序。

还有一些 AVX512 操作似乎可以满足需要,但我不能依赖 AVX512,它在许多目标机器上都不存在...

【问题讨论】:

_mm256_permute4x64_epi64 或其他改组操作之后可以修复订单吗? 我想你的意思是__m256i__m256 是一个单精度浮点向量。 【参考方案1】:

不,您不能在 AVX/AVX2 的单个指令中执行此操作。

有 _mm256_packus_epi16() 但它使用来自第一个输入的前 8 个字节,然后来自第二个输入的前 8 个字节,然后来自第一个和第二个输入的第二个 8 个字节......导致 8 个字节的组无序。

以下是正确排列的方法 (AVX2):

static inline __m128i convert(__m256i data) 
  __m128i lo_lane = _mm256_castsi256_si128(data);
  __m128i hi_lane = _mm256_extracti128_si256(data, 1);
  return _mm_packus_epi16(lo_lane, hi_lane);

根据 Skylake 上的uops.info _mm256_extracti128_si256 在 p5 上是 1 µop,_mm_packus_epi16 在 p5 上是 1 µop。这意味着该代码块的吞吐量应该是 2 个周期(每两个周期进行一次转换)。

您可以使用 _mm256_extractf128_si256 定位 AVX。跨域可能会产生额外的延迟(但吞吐量应该与 AFAIK 相同)。

【讨论】:

太棒了!!谢谢! @AlexanderNovikov:如果你有很多数据要以这种方式处理,通常最好在 2 个输入上使用 _mm256_packus_epi16,然后使用 _mm256_permute4x64_epi64 来修复由 AVX2 在-车道包。一个 32 字节向量的 2 次 shuffle 显然比你从中得到的两个 16 字节向量的 4 次 shuffle 好(AMD Zen1 除外,vextracti128 非常便宜,而跨车道的 256 位 shuffle 很昂贵) 我在Fastest way to downcast an array short to char 上的回答显示了如何在一对__m256i 16x 16 位上使用pack + vpermq 修复以正确的顺序获得一个__m256i 32x 8 位。跨度>

以上是关于将 16 位值的 __m256i 打包(饱和)到 8 位值的 __m128i?的主要内容,如果未能解决你的问题,请参考以下文章

将 __m256i 设置为两个 __m128i 值的值

将__m256i的前N位或后N位设置为1,其余为0的有效方法

将打包的半字节组合成打包的字节

使用最少的指令将 4 个单精度浮点数加载并复制到打包的 __m256 变量中

SSE指令:字节+短

SIMD (AVX2) - 将 uint8_t 值加载到多个浮点 __m256 寄存器