Встроенные функции SSE для сравнения (_mm_cmpeq_ps) и операции присваивания

Я начал оптимизировать свой код с помощью 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, взяв абсолютное значение этого случая, а затем продолжить расчет в обычном режиме.

Однако я не добился больших успехов в этом начинании.


person cubiclewar    schedule 04.11.2011    source источник
comment
Что не так с делением на ноль?   -  person Pubby    schedule 04.11.2011
comment
Помимо очевидных проблем, он искажает результаты, создавая несогласованность при Nz = -1 для остальной части алгоритма.   -  person cubiclewar    schedule 04.11.2011


Ответы (1)


Вот довольно простое решение, которое просто реализует скалярный код с SSE без какой-либо дальнейшей оптимизации. Вероятно, его можно сделать немного более эффективным, например. используя тот факт, что результат будет 0,5, когда wZ = -1,0, или, возможно, даже просто выполнив деление независимо, а затем преобразовав INFs в 0,5 постфактум.

Я выбрал #ifdefd для SSE4 по сравнению с pre-SSE4, поскольку в SSE4 есть инструкция «смешивания», которая может быть немного более эффективной, чем три инструкции pre-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 = %vf\n", wZ);
   printf("d = %vf\n", d);

   return 0;
}
person Paul R    schedule 04.11.2011
comment
именно то, что я был после. Есть несколько операций, которые мне нужно будет прочитать, чтобы полностью понять код, но я получаю правильные результаты. Из любопытства можно ли легко определить в SSE и заменить inf или nan (что бы ни оценивалось 1/0)? - person cubiclewar; 04.11.2011
comment
Я не пробовал, но думаю, что вы можете использовать тот факт, что _mm_cmpeq_ps(v, v) вернет false, когда v равно INF или NaN. Я могу попробовать другое решение с использованием этого метода позже, если у меня будет время. . - person Paul R; 04.11.2011
comment
Я попробовал предложение @PaulR отфильтровать INF/NaN, используя _mm_cmpeq_ps(v, v) в качестве битовой маски, и, похоже, это работает нормально. - person Rotem; 30.04.2015
comment
Он работает для идентификации INF или NaN. Чтобы различить их, вы можете сравнить абсолютное значение с _mm_set1_ps(__builtin_inff()) или в Windows с _mm_set1_ps(HUGE_VALF) - person Jens Munk; 18.06.2015