【问题标题】:How do I convert from larger integers to smaller integers using AVX2 and SSE?如何使用 AVX2 和 SSE 从较大的整数转换为较小的整数?
【发布时间】:2022-01-22 23:38:45
【问题描述】:

有没有一种有效的方法可以使用 AVX2 和 SSE 将较大的整数类型转换为较小的整数类型(当然还有截断)?

如:

  • int16 -> int8
  • int32 -> int16 / int32 -> int8
  • int64 -> int32 / int64 -> int16 / int64 -> int8

我知道 AVX-512 有说明:

  • vpmovqb
  • vpmovwb

对应的内在函数如下:

  • _mm512_cvtepi16_epi8(AVX512 字节和字 ISA)
  • _mm512_cvtepi32_epi8(AVX512 基础)
  • _mm512_cvtepi32_epi16(AVX512 基础)
  • _mm512_cvtepi64_epi8(AVX512 基础)
  • _mm512_cvtepi64_epi16(AVX512 基础)
  • _mm512_cvtepi64_epi32(AVX512 基础)

它处理整数类型缩小转换,但是在没有此类指令的 AVX2 和 SSE 中如何实现这一点?

请注意,虽然上述 AVX512 内部函数有 128 位和 256 位重载,但它们在运行时仍需要 AVX512。我正在寻找仅使用 AVX2 和/或 SSE 指令来完成相同任务的方法。

如果您不想给我我理解的完整答案,我知道很多要问的问题。请向我解释或帮助我找到可以为每次转换复制的算法。

另外请说明您的答案是否适用于有符号和无符号整数或其他。

非常感谢。

澄清;我不打算稍后解压这些数据。我想在许多应用程序中使用矢量化缩小转换,例如:

  • 代码页转换(即UTF32 -> UTF8)(我知道不是转换数据类型那么简单)
  • 数据库翻译(连接2个完全不同的数据库,并在表中单元格的数据类型不同的情况下在它们之间传输数据)
  • 快速数学逼近
  • 还有更多!

几乎可以做任何事情: int32_t y = some_func(); int8_t x = static_cast(y);

但矢量化。我已经测量了我们代码的性能(抱歉,NDA,这里不能显示),发现我们在类型转换上花费了很多时间。特别是缩小转换。编译器在使用 AVX2 可用的指令对带有符号扩展的小->大转换进行矢量化方面做得很好,但缩小转换似乎根本没有被矢量化。

还要澄清一些。我想要一种行为类似于 static_cast 的转换方法(如截断)

【问题讨论】:

  • 这取决于您希望结果去哪里,例如它们如何在向量上打包/传播。也许“默认”的答案是将结果紧紧地打包在一起,这实际上是一个洗牌(AVX2 有这些)。但还有其他选择。例如:将结果留在每个其他元素中(散布着垃圾),这会花费 zero 指令(输入已经是那种形式)。或者穿插零,这需要一个 AND 指令。你需要什么?
  • 或者,如果您这样做只是为了 SIMD 处理的数据密度但稍后解包(或者如果顺序无关紧要),请与 psllw 交错 8 / pand / @987654323 @ 可以将两个向量中的元素组合成包含它们的 16 位元素的低/高半部分。
  • 现有一些关于包装的Q&A,很多只涉及你提到的一个具体案例;在所有此类问答中,您会发现针对不同元素大小的一些不同策略。一个有用的答案可能会链接其中一些以获取有关特定案例的详细信息。 (例如,单个向量与整个数组,值得将多个数据向量与打包指令树组合在一起,而不是一个 vpshufb
  • 注:如果y 不适合int8_tint8_t x = static_cast<int8_t>(y); 的行为是实现定义的。 “截断”是指“环绕”,例如int8_t(-129) == 127?
  • @chtz:这是一个 x86 问题,所以我很有信心截断意味着截断位模式,就像 _mm512_cvtepi16_epi8 一样。由于 x86 使用 2 的补码有符号整数,是的,这意味着环绕。

标签: c++ c type-conversion simd avx


【解决方案1】:

一个好方法是pshufb 指令,暴露为_mm_shuffle_epi8_mm256_shuffle_epi8 内在函数。在现代 CPU 上,该指令非常快:延迟为 1 个周期,每个时钟可以运行 2 个。

这是一个转换 int32 -> int16 的示例,未经测试。通过更改 shuffle 魔法向量,您可以使用相同的代码实现其余的对话。

// Convert int32 lanes into int16 with truncation
inline __m128i cvtepi32_epi16( __m256i x )
{
    // Make a shuffle constant vector
    // If you also need 16-byte version, move this to global variable,
    // to reuse the lower 16 bytes of the same constant
    const __m256i shuffle = _mm256_setr_epi8(
        0, 1, 4, 5, 8, 9, 12, 13,
        -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1,
        0, 1, 4, 5, 8, 9, 12, 13 );

    // Move these bytes
    // Unfortunately that instruction can only move within 16-byte halves.
    // Fortunately, it can selectively zero out bytes, so a bitwise OR is enough to combine
    x = _mm256_shuffle_epi8( x, shuffle );

    // Split into halves
    const __m128i low = _mm256_castsi256_si128( x );
    const __m128i high = _mm256_extracti128_si256( x, 1 );

    // Produce the result
    return _mm_or_si128( low, high );
}

对于 SSE 向量,它比 AVX2 更有效,您只需要一条 _mm_shuffle_epi8 指令。顺便说一句,对于 SSE 和 AVX,该指令 can load 第二个参数直接来自内存,即即使该代码没有内联,它也不需要单独的加载来获取排列常数。

附:有符号/无符号与截断无关,两者之间的向下转换操作相同。有符号/无符号仅在您想要饱和而不是截断时才重要,但static_cast 不会使任何内容饱和,它会截断。

【讨论】:

  • 对于 i64->i32 使用 vpermd 用于 __m256i,并使用 pshufd 用于 __m128i。
  • @aqrit: 或 shufps 从两个 128 位源向量中打包双字。就像extract 之后的这里一样,如果您只有 AVX1 但出于某种原因仍想打包 __m256i 的 4 字节块。 (你是对的,对于 AVX2,你只需使用一个 vpermd 来处理整个事情,而不是两个单独的随机播放加上一个 OR!)
  • @PeterCordes 是的,但是 OP 问了 6 个问题,这回答了所有 6 个问题。固定 shuffle 只对其中一个有好处,甚至不能实现 16 字节 int64->int32 因为它可以'不要选择性地将车道归零。
  • 当然,但作为您的示例,您选择了一种比 vpshufb + extract + 甚至单个向量更好的策略的情况,更不用说多个数组了例如,一个应该vpand / vpackssdw / vpermq 的向量。
  • Re:16 字节的情况:当然,如果您在循环中有单独的 16 字节向量,并且实际上需要它们零扩展(而不是说 @987654338 @ 两个在一起 + movlps / movhps 存储成对的 int32_t)。但是它需要加载一个可能在缓存中丢失的常量(如果您只使用它一次,而不是在循环中重复),如果您关心高半部分为零,您也可以使用零向量shufps出于某种原因,而不仅仅是 pshufdmovqmovlps 提供给 reg 或 memory。
【解决方案2】:

各种 PACK 指令将 int16->int8 或 int32->int16 从一对 mmx/xmm reg 转换为单个 reg,有符号或无符号饱和(对于超出目标类型范围的值)虽然不是 int64->int32 版本。

【讨论】:

  • 该问题要求截断,因此在将数据与饱和包(AVX-512 之前唯一可用的类型)一起使用之前,您需要 _mm256_and_si256 您的数据,然后如果您需要订单匹配原始数组,进行车道交叉 vpermq shuffle 以修复结果。对于 64->32,有 vpermd。 (可能用vpsllq ymm, 32 / vpblendd 将两个 YMM 向量的有用部分合二为一,然后重新排序。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多