从 __m128i 中查找最小值/最大值

Posted

技术标签:

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

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

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

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

【问题讨论】:

如果有帮助,试试software.intel.com/sites/landingpage/IntrinsicsGuide 对于那里记录的大多数内在函数,有详细的伪代码表示它的确切作用 【参考方案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 位标志在运行时检查支持。

以上是关于从 __m128i 中查找最小值/最大值的主要内容,如果未能解决你的问题,请参考以下文章

在 Pandas、Python 中查找具有相同第一列的所有行的最小值、最大值、平均值

查找数组的最小值和最大值

利用Python迭代器查找最小值和最大值

[M贪心] lc1877. 数组中最大数对和的最小值(贪心+双周赛53_2)

Cut the Sequence

从数据文件中的一行整数中查找最大值和最小值