Этот вопрос кажется похожим на Получить максимальное значение в векторе __m128i с SSE? но с шортами и минимумом вместо целого + максимум. Вот что я придумал:
typedef short int weight;
weight horizontal_min_Vec4i(__m128i x) {
__m128i max1 = _mm_shufflehi_epi16(x, _MM_SHUFFLE(0, 0, 3, 2));
__m128i max1b = _mm_shufflelo_epi16(x, _MM_SHUFFLE(0, 0, 3, 2));
__m128i max2 = _mm_min_epi16(max1, max1b);
//max2 = _mm_min_epi16(max2, x);
max1 = _mm_shufflehi_epi16(max2, _MM_SHUFFLE(0, 0, 0, 1));
max1b = _mm_shufflelo_epi16(max2, _MM_SHUFFLE(0, 0, 0, 1));
__m128i max3 = _mm_min_epi16(max1, max1b);
max2 = _mm_min_epi16(max2, max3);
return min(_mm_extract_epi16(max2, 0), _mm_extract_epi16(max2, 4));
}
Функция в основном делает то же самое, что и ответ в https://stackoverflow.com/a/18616825/1500111 для верхней и нижней частей х. Итак, я знаю, что минимальное значение находится в позиции 0 или 4 переменной __m128i max2. Хотя это намного быстрее, чем функция без SIMD horizontal_min_Vec4i_Plain(__m128i x)
показано ниже, я боюсь, что узким местом является _mm_extract_epi16 operation
на последней строке. Есть ли лучший способ добиться этого, для лучшего ускорения? Я использую Haswell, поэтому у меня есть доступ к последним расширениям SSE.
weight horizontal_min_Vec4i_Plain(__m128i x) {
weight result[8] __attribute__((aligned(16)));
_mm_store_si128((__m128i *) result, x);
weight myMin = result[0];
for (int l = 1; l < 8; l++) {
if (myMin > result[l]) {
myMin = result[l];
}
}
return myMin;
}
Сравнение со знаком и без знака почти одинаково, за исключением того, что диапазон с установленным верхним битом рассматривается как больший, чем диапазон с верхним битом, не установленным в сравнениях без знака, и как меньший в сравнениях со знаком. Это означает, что сравнения со знаком и без знака могут быть преобразованы друг в друга по следующим правилам:
x <s y = (x ^ signbit) <u (y ^ signbit)
x <u y = (x ^ signbit) <s (y ^ signbit)
Эта собственность переходит непосредственно к min
а также max
, так:
min_s(x, y) = min_u(x ^ signbit, y ^ signbit) ^ signbit
И тогда мы можем использовать _mm_minpos_epu16
обрабатывать горизонтальный минимум, чтобы получить, в целом, что-то вроде
__m128i xs = _mm_xor_si128(x, _mm_set1_epi16(0x8000));
return _mm_extract_epi16(_mm_minpos_epu16(xs), 0) - 0x8000;
- 0x8000
является ^ 0x8000
и знак-расширение (extract
ноль-расширяет) свернул в одну.