【问题标题】:Find min/max value from a __m128i从 __m128i 中查找最小值/最大值
【发布时间】:2016-12-05 23:59:42
【问题描述】:

我想使用 SIMD 操作在字节数组中找到最小值/最大值。到目前为止,我能够遍历数组并将最小/最大值存储到 __m128i 变量中,但这意味着我正在寻找的值是混合的(确切地说是其他 15 个)。

我发现这些讨论 herehere 用于整数,this page 用于浮点,但我不明白 _mm_shuffle* 是如何工作的。所以我的问题是:

  1. 我必须执行哪些 SIMD 操作才能从 __m128i 变量中提取最小/最大字节(或无符号字节)值?
  2. _mm_shuffle* 是如何工作的?当我在线查看“最小”文档时,我不明白。我知道它与_MM_SHUFFLE macro 有关,但我不明白这个例子。

【问题讨论】:

标签: c++ x86 sse simd


【解决方案1】:

这是uint8_t 的水平最大值示例:

#include "tmmintrin.h" // requires SSSE3

__m128i _mm_hmax_epu8(const __m128i v)
{
    __m128i vmax = v;

    vmax = _mm_max_epu8(vmax, _mm_alignr_epi8(vmax, vmax, 1));
    vmax = _mm_max_epu8(vmax, _mm_alignr_epi8(vmax, vmax, 2));
    vmax = _mm_max_epu8(vmax, _mm_alignr_epi8(vmax, vmax, 4));
    vmax = _mm_max_epu8(vmax, _mm_alignr_epi8(vmax, vmax, 8));

    return vmax;
}

最大值将在所有元素中返回。如果您需要将值作为标量,请使用_mm_extract_epi8

如何将其调整为最小值和带符号的最小值/最大值应该是相当明显的。

【讨论】:

  • 非常感谢,我会尽快测试!
  • 您可以使用 PSHUFD (_mm_shuffle_epi32) 保存一些 MOVDQA 指令,用于最后两次随机播放,因为它们的粒度 >= 4。如果您不需要结果广播,那么您可以去以相反的顺序,将高半部分降低以与低半部分对齐。这将允许使用 PSHUFLW 进行单词洗牌,再次利用它是移动+洗牌的事实。 (PALIGNR 就地更新其目的地,因此如果没有 AVX,编译器必须复制 vmax,因此它仍然具有原始作为 PMAXUB 的输入)。
  • @PeterCordes:是的,关于洗牌的好点 - 我假设这不会对性能至关重要,例如仅在最小/最大减少的最后一步需要,但如果是,那么可能值得按照您的建议进行改进。
  • @PeterCordes:有趣的是,clang 就像你建议的那样使用PSHUFDs 进行最后两次洗牌:godbolt.org/g/Q7e4U4
  • 不错!查看 clang 的 asm 输出并使用它来改进 C 实现以从其他编译器获得更高效的代码通常是一个好主意。我肯定使用了我没有想到但当场发现的技巧。无论如何,即使不是性能关键,由于 uop-cache / I-cache 的原因,节省代码大小总是可能是一个胜利。每次都使用相同的指令有点好,但水平 - 无论是一种足够常见的模式,一系列不同的整数洗牌不应该让未来的读者感到太困惑。
【解决方案2】:

或者,转换为单词并使用phminposuw(未测试)

int hminu8(__m128i x)
{
  __m128i l = _mm_unpacklo_epi8(x, _mm_setzero_si128());
  __m128i h = _mm_unpackhi_epi8(x, _mm_setzero_si128());
  l = _mm_minpos_epu16(l);
  h = _mm_minpos_epu16(h);
  return _mm_extract_epi16(_mm_min_epu16(l, h), 0);
}

根据我的快速计算,延迟比 min/shuffle 级联要差一些,但吞吐量要好一些。不过,phminposuw 的链接答案可能更好。适用于无符号字节(但未测试)

uint8_t hminu8(__m128i x)
{
  x = _mm_min_epu8(x, _mm_srli_epi16(x, 8));
  x = _mm_minpos_epu16(x);
  return _mm_cvtsi128_si32(x);
}

您也可以将其用于最大值,但会产生一些开销:补充输入和结果。

【讨论】:

  • 请注意,这需要 SSE 4.1,即 Intel Penryn 或更高版本。可以通过使用 CPUID 并测试 SSE41 位标志在运行时检查支持。
猜你喜欢
  • 1970-01-01
  • 2012-09-16
  • 2016-08-31
  • 2017-04-20
  • 2013-11-12
  • 2016-06-07
  • 2019-02-25
  • 2019-02-28
  • 2016-01-05
相关资源
最近更新 更多