我正在尋找一種方法來找到SSE中最小值及其在無符號32位整數(類似於_mm_minpos_epu16)中的位置。我知道我可以通過一系列_mm_min_epu32和shuffle/shift來找到最低限度,但這並不能使我獲得這個位置。水平最小值和SSE中無符號32位整數的位置
有沒有人有這樣做的很酷的方式?
我正在尋找一種方法來找到SSE中最小值及其在無符號32位整數(類似於_mm_minpos_epu16)中的位置。我知道我可以通過一系列_mm_min_epu32和shuffle/shift來找到最低限度,但這並不能使我獲得這個位置。水平最小值和SSE中無符號32位整數的位置
有沒有人有這樣做的很酷的方式?
有可能是一個聰明的方法,但現在這裏的蠻力方法:
#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
)。
你打我回答:-)我可能有不同的方法使用'minpos',但我不確定。最有效的解決方案可能是將結果存儲到一個數組並循環四個元素。 – 2015-02-06 09:35:17
赫 - 你必須在早上早些時候登陸StackOverflow! ;-)我認爲最好的解決方案取決於結果的首選格式是什麼 - 如果向量最小值和掩碼是足夠的,那麼只有5條指令使用上述方法,但是如果需要實際索引,那麼我懷疑可能存在一個更好的方法。 – 2015-02-06 09:54:30
你能想到一個關鍵的情況嗎?爲什麼'_mm_minpos_epu16'甚至存在? – 2015-02-06 10:12:14
通常情況下,如果使用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位中。
好主意顛倒面具,然後使用'_mm_minpos_epu16'獲得2倍索引! – 2015-02-09 14:02:19
@PaulR,謝謝。是的,太糟糕了SSE沒有'!='或者'>'無符號的。 XOP和AVX512都有它們。 – 2015-02-09 14:10:04
你想把位置作爲索引值嗎(比如'_mm_minpos_epu16')或者掩碼是OK(min元素被設置爲-1,所有其他元素都被設置爲0)? – 2015-02-06 08:43:56
請問你爲什麼要這麼做?我無法想象爲什麼你想在循環中每次迭代都要這樣做。爲什麼這是關鍵?我猜如果我知道爲什麼英特爾首先創建了'_mm_minpos_epu16',那將會有所幫助。 – 2015-02-06 10:00:58