#c #sse #intrinsics
#c #sse #встроенные функции
Вопрос:
Я начал оптимизировать свой код с помощью SSE. По сути, это трассировщик лучей, который обрабатывает 4 луча одновременно, сохраняя координаты в __m128 типах данных x, y, z (координаты для четырех лучей сгруппированы по оси). Однако у меня есть разветвленный оператор, который защищает от деления на ноль, который, похоже, я не могу преобразовать в SSE. В последовательном это:
const float d = wZ == -1.0f ? 1.0f/( 1.0f-wZ) : 1.0f/(1.0f wZ);
Где wZ — координата z, и это вычисление необходимо выполнить для всех четырех лучей.
Как я мог бы перевести это в SSE?
Я экспериментировал с использованием сравнения SSE equals следующим образом (теперь wz относится к типу данных __m128, содержащему значения z для каждого из четырех лучей):
_mm_cmpeq_ps(_mm_set1_ps(-1.0f) , wZ )
А затем использовать это для определения случаев, когда wZ [x] = -1.0, принимая абсолютное значение этого случая, а затем продолжить вычисление в обычном режиме.
Однако я не добился большого успеха в этом начинании.
Комментарии:
1. Что не так с делением на ноль?
2. Помимо очевидных проблем, это искажает результаты, создавая несоответствие при Nz = -1 для остальной части алгоритма.
Ответ №1:
Вот довольно простое решение, которое просто реализует скалярный код с помощью SSE без какой-либо дальнейшей оптимизации. Вероятно, это можно сделать немного более эффективным, например, используя тот факт, что результат будет равен 0,5 при wZ = -1,0, или, возможно, даже просто выполнив деление независимо, а затем преобразовав INF
s в 0,5 после факта.
Я #ifdef
выбрал SSE4 по сравнению с предварительной SSE4, поскольку SSE4 имеет инструкцию «blend», которая может быть немного более эффективной, чем три инструкции до SSE4, которые в противном случае необходимы для маскировки и выбора значений.
#include <emmintrin.h>
#ifdef __SSE4_1__
#include <smmintrin.h>
#endif
#include <stdio.h>
int main(void)
{
const __m128 vk1 = _mm_set1_ps(1.0f); // useful constants
const __m128 vk0 = _mm_set1_ps(0.0f);
__m128 wZ, d, d0, d1, vcmp;
#ifndef __SSE4_1__ // pre-SSE4 implementation
__m128 d0_masked, d1_masked;
#endif
wZ = _mm_set_ps(-1.0f, 0.0f, 1.0f, 2.0f); // test inputs
d0 = _mm_add_ps(vk1, wZ); // d0 = 1.0 - wZ
d1 = _mm_sub_ps(vk1, wZ); // d1 = 1.0 wZ
vcmp = _mm_cmpneq_ps(d1, vk0); // test for d1 != 0.0, i.e. wZ != -1.0
#ifdef __SSE4_1__ // SSE4 implementation
d = _mm_blendv_ps(d0, d1, vcmp);
#else // pre-SSE4 implementation
d0_masked = _mm_andnot_ps(vcmp, d0);
d1_masked = _mm_and_ps(vcmp, d1);
d = _mm_or_ps(d0_masked, d1_masked); // d = wZ == -1.0 ? 1.0 / (1.0 - wZ) : 1.0 / (1.0 wZ)
#endif
d = _mm_div_ps(vk1, d);
printf("wZ = %vfn", wZ);
printf("d = %vfn", d);
return 0;
}
Комментарии:
1. именно то, что мне было нужно. Есть несколько операций, которые мне нужно будет прочитать, чтобы полностью понять код, но я генерирую правильные результаты. Из любопытства можно ли легко идентифицировать inf или nan (что когда-либо оценивается как 1/0) в SSE и заменить?
2. Я не пробовал, но я думаю , что вы можете использовать тот факт, что
_mm_cmpeq_ps(v, v)
будет возвращать false, когдаv
INF
илиNaN
— я могу попробовать другое решение, используя этот метод позже, если у меня будет время…3. Я попробовал предложение @ PaulR отфильтровать
INF
/NaN
использовать_mm_cmpeq_ps(v, v)
в качестве битовой маски, и, похоже, оно работает нормально.4. Он работает для определения INF или NaN. Чтобы провести различие между ними, вы можете сравнить абсолютное значение с _mm_set1_ps(__builtin_inff()) или в Windows, _mm_set1_ps(HUGE_VALF)