SSE中的水平最小值和位置,用于无符号32位整数

时间:2015-02-05 23:17:45

标签: performance algorithm optimization sse simd

我正在寻找一种方法来在SSE中找到无符号32位整数的最小值及其位置(类似于_mm_minpos_epu16)。我知道我可以通过一系列_mm_min_epu32和shuffles / shift来找到最小值,但这并不能让我获得这个位置。

有没有人有这么好的方法呢?

2 个答案:

答案 0 :(得分:3)

可能有一种更聪明的方法,但现在这是一种蛮力方法:

#include <stdio.h>
#include <smmintrin.h> // SSE4.1

int main(void)
{
    __m128i v = _mm_setr_epi32(42, 1, 43, 2);

    printf("v     = %vlu\n", v);

    __m128i vmin = v;

    vmin = _mm_min_epu32(vmin, _mm_alignr_epi8(vmin, vmin, 4));
    vmin = _mm_min_epu32(vmin, _mm_alignr_epi8(vmin, vmin, 8));
                                                   // get min value in all elements of vmin

    printf("vmin  = %vlu\n", vmin);

    __m128i vmask = _mm_cmpeq_epi32(v, vmin);      // set min element(s) in mask to -1,
                                                   // all others to 0 [1]

    printf("vmask = %vld\n", vmask);

    int16_t mask = _mm_movemask_epi8(vmask);       // get mask as scalar [2]

    printf("mask  = %#x\n", mask);

    int pos = __builtin_ctz(mask) >> 2;            // convert scalar mask to index [3]

    printf("pos   = %d\n", pos);

    return 0;
}

如果你可以使用在最小元素的位置设置的掩码,那么你可以停在[1],否则继续[3]得到(最不重要的)的索引)最小元素。

另请注意,__builtin_ctz是一个特定于gcc的内在函数(尽管它也可以在其他gcc兼容的编译器中找到)。如果您正在使用MSVC,那么您需要使用等效的Microsoft内在函数(_BitScanForward)。

答案 1 :(得分:2)

一般情况下,如果使用SIMD的水平算子,则表明SIMD没有得到最佳使用。但是,在循环结束时水平操作很好,在这种情况下我只会这样做

int result[4] __attribute__((aligned(16)));
_mm_store_si128((__m128i *) result, v);
for(int i=0; i<4; i++) if(result[i]<min) { min = result[i]; index = i; }

尽管如此,这里有一些使用SSE的解决方案。我不知道它们是否比上面的代码更好。

第一个解决方案是Paul R答案的变体。

vmin = _mm_min_epu32(vmin, _mm_alignr_epi8(vmin, vmin, 4));
vmin = _mm_min_epu32(vmin, _mm_alignr_epi8(vmin, vmin, 8));
__m128i vmask = _mm_cmpeq_epi32(v, vmin);
vmask = _mm_xor_si128(vmask, _mm_set1_epi32(-1));
__m128i vpos = _mm_minpos_epu16(vmask);

vpos中的第二个16位字包含两倍的位置。

以下是使用_mm_minpos_epu16的另一种变体。它首先找到最小的高16位,然后屏蔽掉不在最小16位的值(通过将它们全部置为高电平),然后它找到低16位的最小值以及位置。

__m128i mask1 = _mm_setr_epi8(0x0,0x1,0x4,0x5, 0x8,0x9,0xc,0xd, 0x0,0x1,0x4,0x5,  0x8,0x9,0xc,0xd);
__m128i mask2 = _mm_setr_epi8(0x2,0x3,0x6,0x7, 0xa,0xb,0xe,0xf, 0x2,0x3,0x6,0x7,  0xa,0xb,0xe,0xf);
__m128i mask3 = _mm_set1_epi32(0x01000100);

掩码是常量的,因此可以在编译时或循环外计算它们。

__m128i lo = _mm_shuffle_epi8(v,mask1);            //lower 16-bits
__m128i hi = _mm_shuffle_epi8(v,mask2);            //upper 16-bits
__m128i t1 = _mm_minpos_epu16(hi);                 //upper 16-bits min
__m128i t2 = _mm_shuffle_epi8(t1, mask3);          //broadcast upper min
__m128i t3 = _mm_cmpeq_epi32(t2,hi);               //select equal
__m128i t4 = _mm_xor_si128(t3, _mm_set1_epi32(-1));//invert
__m128i t5 = _mm_or_si128(lo,t4);                   
__m128i t6 = _mm_minpos_epu16(t5);                 //lower 16-bits hi and position

最小值的高16位在t1的前16位中,最小值的低16位在t6的前16位中。该位置位于t6的第二个16位。