Минимум 4 СП значения в м128 микроконтроллеров
предположим, что есть __m128
переменная, содержащая 4 значения SP, и вы хотите минимальную, есть ли какая-либо внутренняя функция или что-либо другое, кроме наивного линейного сравнения между значениями?
право знать мое решение следующее (предположим, что на входе __m128
переменная x
):
x = _mm_min_ps(x, (__m128)_mm_srli_si128((__m128i)x, 4));
min = _mm_min_ss(x, (__m128)_mm_srli_si128((__m128i)x, 8))[0];
что довольно ужасно, но он работает (кстати, есть ли что-нибудь вроде _mm_srli_si128
но __m128
тип?)
2 ответов
нет одной инструкции / внутренней, но вы можете сделать это с двумя перетасовками и двумя минутами:
__m128 _mm_hmin_ps(__m128 v)
{
v = _mm_min_ps(v, _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3)));
v = _mm_min_ps(v, _mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 0, 3, 2)));
return v;
}
выходной вектор будет содержать минимум всех элементов входного вектора, повсюду в выходной вектор.
ответ Павла R велик! (@Paul R-Если Вы читаете это спасибо!) Я просто хотел попытаться объяснить, как это на самом деле работает для любого нового SSE, такого как я. Конечно Я могу ошибаться где-то, поэтому любые поправки приветствуются!
как _mm_shuffle_ps
работы?
прежде всего, регистры SSE имеют индексы, которые идут в обратном направлении к тому, что вы могли бы ожидать, например:
[6, 9, 8, 5] // values
3 2 1 0 // indexes
этот порядок индексирования делает векторными левыми сдвигами перемещение данных с низкого на высокие индексы, как и левый сдвиг битов в целое число. Самый важный элемент находится слева.
_mm_shuffle_ps
можно смешивать содержимое двух регистров:
// __m128 a : (a3, a2, a1, a0)
// __m128 b : (b3, b2, b1, b0)
__m128 two_from_a_and_two_from_b = _mm_shuffle_ps(b, a, _MM_SHUFFLE(3, 2, 1, 0));
// ^ ^ ^ ^
// indexes into second operand indexes into first operand
// two_from_a_and_two_from_b : (a3, a2, b1, b0)
здесь мы хотим только перетасовать значения одного регистра, а не двух. Мы можем сделать это, передав v как оба параметра, например (вы можете увидеть это в функции Paul R):
// __m128 v : (v3, v2, v1, v0)
__m128 v_rotated_left_by_1 = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3));
// v_rotated_left_by_1 : (v2, v1, v0, v3) // i.e. move all elements left by 1 with wraparound
я собираюсь обернуть его в макрос для удобочитаемости, хотя:
#define mm_shuffle_one(v, pattern) _mm_shuffle_ps(v, v, pattern)
(It не может быть функцией, потому что до _mm_shuffle_ps
должно быть постоянным во время компиляции.)
вот немного измененная версия фактической функции – я добавил промежуточные имена для удобочитаемости, так как компилятор оптимизирует их в любом случае:
inline __m128 _mm_hmin_ps(__m128 v){
__m128 v_rotated_left_by_1 = mm_shuffle_one(v, _MM_SHUFFLE(2, 1, 0, 3));
__m128 v2 = _mm_min_ps(v, v_rotated_left_by_1);
__m128 v2_rotated_left_by_2 = mm_shuffle_one(v2, _MM_SHUFFLE(1, 0, 3, 2));
__m128 v3 = _mm_min_ps(v2, v2_rotated_left_by_2);
return v3;
}
почему перемешивание элементов так, как мы? И как мы находим наименьший из четырех элементов только с двумя min
операции?
у меня были некоторые проблемы с тем, как вы можете min
4 поплавки с двумя векторизованными min
операции, но я понял это, когда я вручную, следует, что значения min
будем вместе, шаг за шагом. (Хотя это, вероятно, более интересно сделать это самостоятельно, чем читать его)
скажем, у нас есть v
:
[7,6,9,5] v
во-первых, мы min
значения v
и v_rotated_left_by_1
:
[7,6,9,5] v
3 2 1 0 // (just the indices of the elements)
[6,9,5,7] v_rotated_left_by_1
2 1 0 3 // (the indexes refer to v, and we rotated it left by 1, so the indices are shifted)
--------- min
[6,6,5,5] v2
3 2 1 0 // (explained
2 1 0 3 // below )
каждый столбец под элементом v2
трассы какие показатели v
были min
'D вместе, чтобы получить этот элемент.
Итак, идя колонной слева направо:
v2[3] == 6 == min(v[3], v[2])
v2[2] == 6 == min(v[2], v[1])
v2[1] == 5 == min(v[1], v[0])
v2[0] == 5 == min(v[0], v[3])
второе min
:
[6,6,5,5] v2
3 2 1 0
2 1 0 3
[5,5,6,6] v2_rotated_left_by_2
1 0 3 2
0 3 2 1
--------- min
[5,5,5,5] v3
3 2 1 0
2 1 0 3
1 0 3 2
0 3 2 1
вуаля! Каждая колонка под v3
содержит (3,2,1,0)
- каждый элемент v3
была min
D со всеми элементами v
- таким образом, каждый элемент содержит минимум всего вектора v
.
после использования функции вы можете извлечь минимальное значение с помощью float _mm_cvtss_f32(__m128)
:
__m128 min_vector = _mm_hmin_ps(my_vector);
float minval = _mm_cvtss_f32(min_vector);
***
это просто тангенциальная мысль, но я нашел интересным то, что этот подход может быть расширен до последовательностей произвольной длины, вращая результат предыдущего шага на 1, 2, 4, 8, ... 2**ceil(log2(len(v)))
(я думаю) на каждом шаге.
Это круто с теоретической точки зрения - если вы можете сравнить две последовательности по элементам одновременно, вы можете найти минимум/максимум1 последовательностей в логарифмическом время!
1 это распространяется на все горизонтальные складки / сокращения, такие как sum. Те же перетасовки, другая вертикальная операция.
однако AVX (256-битные векторы) делает 128-битные границы особенными и сложнее перетасовать. Если вам нужен только скалярный результат, извлеките верхнюю половину, чтобы каждый шаг сужал ширину вектора пополам. (Как в самый быстрый способ сделать горизонтальную сумму вектора поплавка на x86, который имеет более эффективные перетасовки чем 2x shufps
для 128-битных векторов, избегая некоторых movaps
инструкции при компиляции без AVX.)
но если вы хотите, чтобы результат транслировался на каждый элемент, такой как ответ @PaulR, вы хотели бы сделать перетасовки в полосе (т. е. вращаться в пределах 4 элементов в каждой полосе), а затем поменять местами половинки или повернуть 128-битные полосы.