如何将 8 个打包的 32 位整数(在一个 __m256i)的 +-1 个符号打包成 64 位整数的字节?

How to pack +-1 signs of 8 packed 32-bit integers (in an __m256i) into bytes of a 64-bit integer?

提问人:Serge Rogatch 提问时间:8/20/2023 更新时间:8/21/2023 访问量:152

问:

给定一个打包的 32 位有符号整数,如果原始的相应 32 位有符号整数大于或等于 0,如何获得每个字节的单个 64 位数字,如果该 32 位整数为负数?__m256i1__m256i-1

AVX2(可能还有 AVX512)值得关注。

C++ 性能 SIMD 内部函数 AVX2

评论

0赞 Peter Cordes 8/21/2023
你打算用 +1 / -1 字节做什么?您希望它们位于 SIMD 向量、内存中的数组、整数寄存器中还是什么?您是否有多个输入向量可以转换为更大的符号字节数组,因此使用 2 输入包生成 32 字节的 4 输入函数会很有用?他们会成为其他东西的面具吗?为什么不是 0 / -1,生成成本更低(例如 还是比较)?如果是 AVX-512,您能否将符号位提取到掩码寄存器中,并在接下来要执行的任何操作中使用合并屏蔽操作?int64_t__m256i_mm256_srai_epi32(v, 31)
0赞 Serge Rogatch 8/22/2023
@PeterCordes然后我要将它们乘以数字,请参阅 stackoverflow.com/questions/76947456/...。为了节省CPU缓存,正在进行棘手的打包。我有多个向量 - 大约 4K 的总标量值。是的,我想要 4 个这样的 64 位整数到一个寄存器中。(-1, 0, 1)__m256i
1赞 harold 8/22/2023
乘以 (-1, 0, 1) 在字节 (psignb) 上非常容易
0赞 Peter Cordes 8/22/2023
x86 没有 SIMD 8 位乘法,除了将水平对累积成 16 位乘积的总和。因此,您实际上希望根据 32 位元素的符号位有条件地否定其他内容。最有效的方法不涉及创建 +1 / -1 的向量。一个更好的方法是打包 to 或字节元素,您可以将其与 XOR 和 ADD 一起使用以否定或不否定。 (0 和 signs=-1 的空操作也是如此,与通常的 bithack abs() 相同,但不适用于自身)pmaddubswint8_t0-1-x = ~x + 1(x ^ signs) - signs-x
0赞 Peter Cordes 8/22/2023
因此,您只想获得具有相同符号位的 16 位元素,然后广播符号位。或者实际上用有符号的包一直打包到 8 位,然后去做,即根据它的符号位设置字节。或者,代替那个 + bithack,用于在原始和结果之间进行选择。但是,也许您可以在解码 2 位压缩字段时更有效地使用 0 / -1,例如在查找之前使用一个 XOR 或 SUB?_mm256_packs_epi32(v0,v1)_mm256_srai_epi16(tmp, 15)_mm256_cmpgt_epi8( _mm256_setzero_si256(), tmp)0 > vvpblendvb0 - xvpshufb

答:

1赞 Alex Guteniev 8/21/2023 #1

您可以将结果转换为 +1 / -1 作为向量,然后使用一系列包对其进行压缩。__m256i

第一步可以完成:

  • 使用后跟 with ,正如 Peter Cordes 所建议的那样_mm256_srai_epi32(val, 31)or_mm256_set1_epi32(1)
  • 正如 Harold 所建议的那样使用 ,然后是 with 正如 Peter Cordes 所观察到的那样_mm256_sign_epi32(_mm256_set1_epi32(1), values)or_mm256_set1_epi32(1)
  • 像这样,知道符号和掩码位 和 处于同一位置:floatint32_t
__m256i plus_or_minus_pi32 =  _mm256_castps_si256(_mm256_blendv_ps(
  _mm256_castsi256_ps(_mm256_set1_epi32(+1)),
  _mm256_castsi256_ps(_mm256_set1_epi32(-1))
  _mm256_castsi256_ps(val)));

然后是随机播放,然后另一个可以将其压缩到向量。_mm256_packs_epi32_mm256_packs_epi16int8_t

最后,如果需要,使用零参数转换为 GRP。_mm256_extract_epi64

评论

1赞 Peter Cordes 8/21/2023
另一种策略是从算术右移 31 () 开始,得到 0 / -1 值,然后是 OR 和 After Packing。由于在 Intel CPU 上花费 2 uops,因此这是领先的(特别是如果将多个向量打包为一个,或者您可以在标量中执行 OR)。它还避免了向量常量。请注意,AVX2 打包说明不是跨车道的,因此您需要在两轮车道内打包后进行洗牌。_mm256_srai_epi32(v, 1)set1_epi8(1)blendv
0赞 Peter Cordes 8/21/2023
@harold:vpsignd 将 input = 0 时的值归零。在这种情况下,OP 需要,所以我们仍然必须在末尾标量 OR with 将任何零变成 1,并保持任何 -1 不变。移位产生 0 / -1,无需向量常数。10x0101010101010101
1赞 Peter Cordes 8/21/2023
@harold:您使用的标量策略可以用 OR 和 来固定,编译器在寄存器中已经需要相同的常量,因此它是有效的(Zen 2 及更早版本除外)。我认为,如果你只有一个向量,这在总 uop 计数上实际上是相当不错的。pdep0x01...pdep
3赞 Soonts 8/21/2023 #2

这是另一种方法。

它需要 BMI2 支持 PDEP 指令,仅在 Intel 和 AMD 上快速,从 Zen 3 微架构开始。

// Convert sign bits of 8 int32 lanes into -1 / +1 bytes
uint64_t packSigns( __m256i vec )
{
    // Bitcast to FP32 vector, compiles into no instructions
    __m256 fv = _mm256_castsi256_ps( vec );
    // Move sign bits to general-purpose register
    uint64_t bits = (uint32_t)_mm256_movemask_ps( fv );
    // Expand bits into bytes
    constexpr uint64_t lowBits = 0x0101010101010101ull;
    bits = _pdep_u64( bits, lowBits );
    // Convert 0 / +1 bytes into +1 / -1 bytes
    return ( bits * 0xFF ) | lowBits;
}

评论

0赞 Soonts 8/21/2023
@harold 问得好。查看编辑,保存了一条指令。