【问题标题】:Horizontal minimum and maximum using SSE使用 SSE 的水平最小值和最大值
【发布时间】:2014-04-10 23:31:25
【问题描述】:

我有一个使用 SSE 做很多事情的函数,分析器显示我用来计算水平最小值和最大值的代码部分大部分时间都在消耗。

例如,我一直在使用以下实现:

static inline int16_t hMin(__m128i buffer) {
    buffer = _mm_min_epi8(buffer, _mm_shuffle_epi8(buffer, m1));
    buffer = _mm_min_epi8(buffer, _mm_shuffle_epi8(buffer, m2));
    buffer = _mm_min_epi8(buffer, _mm_shuffle_epi8(buffer, m3));
    buffer = _mm_min_epi8(buffer, _mm_shuffle_epi8(buffer, m4));
    return ((int8_t*) ((void *) &buffer))[0];
}

如你所见,我需要计算 16 个 1 字节整数的最小值和最大值。

非常感谢任何好的建议:)

谢谢

【问题讨论】:

  • 掩码m1, m2, m3, and m4的值是多少?
  • 不知道有没有更好的办法。如果没有水平最大最小值运算符,您必须分四个二进制步骤进行操作(我认为您正在这样做):将 8 与 8 进行比较,然后将 4 与 4 进行比较,然后将 2 与 2 进行比较,然后将 1 与 1 进行比较。使用 int4 需要两个步骤:stackoverflow.com/questions/9877700/…
  • 是的,抱歉,我忘记了随机播放控制掩码。它们被用作之前假设的 Z 玻色子
  • 正如 Marat Dukhan 的回答中强调的主要原因,采用 SIMD 向量的地址不是将元素值提取到 CPU 寄存器中的正确方法,因为该样式将强制将值写入记忆。 (也不直接访问“结构”的值。)将代码更改为_mm_cvtsi128_si32 将是最好的做法。
  • 可能值得一提的是,如果您的调用代码有(远)超过 16 个 1 字节整数来找到总体最小值,那么您只需累积单个 _mm_min_epi8 的结果即可更快地完成此操作操作成一个 __m128i 值,并在你的函数中执行最后一次合并结果的步骤。

标签: c++ max sse minimum avx


【解决方案1】:

SSE 4.1 的指令几乎可以满足您的需求。它的名字是PHMINPOSUW,C/C++内在是_mm_minpos_epu16。它仅限于 16 位无符号值,不能给出最大值,但这些问题很容易解决。

  1. 如果您需要找到最少的非负字节,则什么也不做。如果字节可能为负数,则每个字节加 128。如果您需要最大值,请从 127 中减去每个。
  2. 使用_mm_srli_pi16_mm_shuffle_epi8,然后使用_mm_min_epu8 在某些XMM 寄存器的偶数字节中获得8 个成对的最小值,在奇数字节中获得零。 (这些零是由移位/洗牌指令产生的,应该在_mm_min_epu8 之后保留在它们的位置。
  3. 使用_mm_minpos_epu16 找出这些值中的最小值。
  4. 使用_mm_cvtsi128_si32 提取得到的最小值。
  5. 撤消步骤 1 的效果以获取原始字节值。

这是一个最多返回 16 个有符号字节的示例:

static inline int16_t hMax(__m128i buffer)
{
    __m128i tmp1 = _mm_sub_epi8(_mm_set1_epi8(127), buffer);
    __m128i tmp2 = _mm_min_epu8(tmp1, _mm_srli_epi16(tmp1, 8));
    __m128i tmp3 = _mm_minpos_epu16(tmp2);
    return (int8_t)(127 - _mm_cvtsi128_si32(tmp3));
}

【讨论】:

  • 我在基准测试中也尝试了您的方法,它确实比其他方法快一点。
  • 很好,我喜欢 _mm_min_epu8 如何在每个 16 位元素的高半部分留下零,因为 unsigned_min(0,x) = 0。所以没有指令只用于零扩展16 位。
  • 请注意,减去 128 与与 128 相加或异或相同(因为进位无处可去)。 pxor 运行在比 psubb 更多的端口上(并且是可交换的,使优化器在寄存器分配方面具有更大的灵活性),所以当范围转移到无符号时你应该更喜欢它。
【解决方案2】:

我建议进行两个更改:

  • ((int8_t*) ((void *) &buffer))[0] 替换为_mm_cvtsi128_si32
  • _mm_shuffle_epi8 替换为_mm_shuffle_epi32/_mm_shufflelo_epi16,这在最近的 AMD 处理器和 Intel Atom 上具有更低的延迟,并且可以节省内存加载操作:

    static inline int16_t hMin(__m128i buffer)
    {
        buffer = _mm_min_epi8(buffer, _mm_shuffle_epi32(buffer, _MM_SHUFFLE(3, 2, 3, 2)));
        buffer = _mm_min_epi8(buffer, _mm_shuffle_epi32(buffer, _MM_SHUFFLE(1, 1, 1, 1)));
        buffer = _mm_min_epi8(buffer, _mm_shufflelo_epi16(buffer, _MM_SHUFFLE(1, 1, 1, 1)));
        buffer = _mm_min_epi8(buffer, _mm_srli_epi16(buffer, 8));
        return (int8_t)_mm_cvtsi128_si32(buffer);
    }
    

【讨论】:

  • 我今天测试了这个方法,它适用于大多数情况,但是当它位于第一个位置时,它无法找到最小值,例如如果您使用向量 (0,1,..,15) 对其进行测试,它将返回最小值为 1。对于所有其他情况,它似乎都有效!
  • @user46317 你是对的,有一个错误。现在它已修复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-01
  • 2016-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多