Преобразование условий в арифметические выражения - PullRequest
0 голосов
/ 16 ноября 2018

Как часть системы частиц, симулирующей гравитацию и столкновения между многими объектами, я наткнулся на странную ошибку.

Это школьный проект, нацеленный на оптимизацию посредством векторизации (OpenMP SIMD), и в рамках оптимизации я хотел избавиться от следующего выражения:

if (r > COLLISION_DISTANCE) {
    resultVelX += gravityVelX;
    // ... same for remaining dimensions
}
if (r > 0.f && r < COLLISION_DISTANCE) {
    resultVelX += collisionVelX;
    // ... same for remaining dimensions
}

Моя идея заключалась в том, чтобы захватить оба условия в переменных, а затем добавить значения к результату без ifs, используя простую арифметику:

const int condGrav = (r > COLLISION_DISTANCE);
const int condCol = (r > 0.f && r < COLLISION_DISTANCE);
resultVelX += condGrav * gravityVelX + condCol * collisionVelX;
// ... same for remaining dimensions

Мы получили набор тестов для этого проекта, и, к моему удивлению, хотя простые тесты прошли в обеих версиях кода, в наиболее сложных случаях он не удался для второй версии, сообщая об ошибке бесконечной точности (e + 616 как я выяснил из самых информативных логов).

Все вычисления выполняются на поплавках. Компиляция выполняется с помощью компилятора intel 2016a icpc.

Вопрос: что не так со вторым фрагментом кода? Это просто неправильно или есть что-то, что я могу пропустить?

Ответы [ 2 ]

0 голосов
/ 16 ноября 2018

Ваше изменение выглядит эквивалентно мне, если предположить, что gravityVelX никогда не является NaN.Логическое значение преобразуется в 0.0 или 1.0.

Если ваше изменение позволяет выполнить оптимизацию, которая ранее была невозможна или не была выполнена, то, возможно, по умолчанию ICC, равный -ffast-math, вызывает проблему.(По умолчанию это -fp-model fast=1: https://software.intel.com/en-us/node/522979. Это похоже на -ffast-math в gcc, которая позволяет оптимизировать изменения, которые меняют результат.)


Кстати, вы можете получить лучшие результаты с этим,потому что SSE2 может делать это напрямую

resultVelX += r > COLLISION_DISTANCE ? gravityVelX : 0.0;

Это наиболее прямо выражает в C то, что вы хотите, чтобы компилятор испускал
(cmpps r,collision_distance / andps gravityVelX, cmp_result / addps resultVelX, and_result),Вы на самом деле не хотите или не должны умножать, и создание фактической 1.0 более громоздко, чем просто добавление 0 или того, что вы хотите.

x86 SIMD-команды сравнения создают вектор из всех нулей или всех единиц.который вы можете использовать в качестве маски И непосредственно.Это прекрасно работает для условного сложения, поскольку битовый шаблон со всеми нулями представляет IEEE 754 0.0, а ноль - аддитивный идентификатор.

(Без -ffast-math компиляторы не всегда могут предположить, что добавление 0.0 не работает. Я думаю, что из-за нулевого знака. Обычно вам нужны дополнительные опции, чтобы сообщить компиляторам, что операции FP могут вызывать исключения, то есть, что исключения не маскируются и, таким образом, являются видимым побочным эффектом. В любом случае, с параметрами ICC по умолчанию,он должен иметь возможность самостоятельно конвертировать if в код без ответвлений, но если у него возникают проблемы, удерживая его вручную с троичным, который всегда добавляет что-то , это путь.)

0 голосов
/ 16 ноября 2018

Псевдокод для сравнения SIMD (SSE) выглядит следующим образом:

__m128 _mm_cmpgt_ps (__m128 a, __m128 b)

FOR j := 0 to 3
    i := j*32
    dst[i+31:i] := ( a[i+31:i] > b[i+31:i] ) ? 0xffffffff : 0
ENDFOR

Результат сравнения не 1.0f, это NaN.
Я не знаю, потому чтоЯ не вижу полный код, но это может быть причиной неправильных вычислений, если вы используете SSE в своей программе.

...