SSE2内置函数 - 找到两个无符号短向量的最大值

3

我希望找出包含8个16位无符号整数元素的两个向量中的最大值。

__m128i vi_A= _mm_loadu_si128(reinterpret_cast<const __m128i*>(&pSrc[0])); // 8 16-Bit Elements
__m128i vi_B= _mm_loadu_si128(reinterpret_cast<const __m128i*>(&pSrc1[0])); // 8 16-Bit Elements
__m128i vi_Max = _mm_max_epi16(vi_A,vi_B);  //<-- Error 

但是_mm_max_epi16是一个有符号整数比较,这会导致溢出问题。 因此我尝试使用SSE4.1内置函数的无符号版本。

vi_Max = _mm_max_epu16(vi_A,vi_B);

但是我不允许使用SSE4.1内部函数,那么有什么高效的方法可以找到这些元素的最大值呢?

1个回答

5

有一种(比较低效的)方法是通过将输入值偏移0x8000,然后将此偏移量加回结果来实现,例如:

#ifndef __SSE4_1__
inline __m128i _mm_max_epu16(const __m128i v0, const __m128i v1)
{
    return _mm_add_epi16(
               _mm_max_epi16(
                   _mm_sub_epi16(v0, _mm_set1_epi16(0x8000)),
                   _mm_sub_epi16(v1, _mm_set1_epi16(0x8000))),
               _mm_set1_epi16(0x8000));
}
#endif

使用gcc或clang编译器,这会生成一个常量的加载指令和四个算术指令。


注意,您可以使用_mm_xor_si128替代_mm_add_epi16/_mm_sub_epi16,这可能更清晰地表达意图,并且在目标架构上可能具有更好的性能:

#ifndef __SSE4_1__
inline __m128i _mm_max_epu16(const __m128i v0, const __m128i v1)
{
    return _mm_xor_si128(
               _mm_max_epi16(
                   _mm_xor_si128(v0, _mm_set1_epi16(0x8000)),
                   _mm_xor_si128(v1, _mm_set1_epi16(0x8000))),
               _mm_set1_epi16(0x8000));
}
#endif

1
哇!感谢您的快速回答!它像魔法一样奏效了! - Balaji R
异或运算很可能更快,但更重要的是,它消除了溢出的可能性。 - Cody Gray
1
@CodyGray 什么溢出?使用0x8000对16位整数进行加法/减法/异或运算都是完全等效的,没有任何边界情况。 - harold
@CodyGray: 在Intel Nehalem/Westmere/SB/IB/Haswell上,_mm_xor_si128 可能_mm_add_epi16/_mm_sub_epi16 更快(吞吐量为0.33比0.5),但OP显然使用的是旧架构,所以情况可能并非如此。像往常一样,最好进行基准测试,但我猜差别不会很大。如上所述,溢出不是问题。还要注意,clang在代码生成期间将上述内容转换为使用_mm_xor_si128,这非常酷。 - Paul R
1
有可能会超出有符号16位整数的范围,但我想这里的说法是如果溢出,paddw 有明确定义的环绕语义,所以这不是问题。但我个人仍然认为XOR更清晰易懂。也许这只是一个愚蠢的主观偏好。XOR通常也能在比ADD/SUB更多的执行单元上运行,这也适用于旧架构。不过,Clang进行转换的功能确实非常酷。 - Cody Gray
1
@CodyGray:是的,经过思考,我认为使用异或可能更清晰,而且由于它至少与使用减法/加法一样高效,所以也许它是更好的解决方案。我会尽快更新答案... - Paul R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接