Как установить отрицательное число в бесконечность без использования оператора if (или троичного) - PullRequest
0 голосов
/ 02 июня 2018

У меня есть следующий фрагмент кода:

for(uint i=0; i<6; i++)
        coeffs[i] = coeffs[i] < 0 ? 1.f/0.f : coeffs[i];

, который проверяет массив с 6 элементами и, если он находит отрицательную запись, устанавливает его в бесконечность и в противном случае оставляет запись без изменений.

Мне нужно сделать то же самое без использования операторов if

Ответы [ 2 ]

0 голосов
/ 02 июня 2018

Один очевидный вопрос: какая бесконечность вам нужна, когда ввод меньше 0.

Любая бесконечность

Если результат может быть отрицательной бесконечностью, я 'd сделать что-то вроде этого:

coeffs[i] /= (coeffs[i] >= 0.0);

coeffs[i] >= 0.0 выдает 1,0, если вход положительный, и 0.0, если вход отрицательный.Деление ввода на 1,0 оставляет его без изменений.Разделив его на 0, мы получим бесконечность.

Положительная бесконечность

Если это положительная бесконечность, вы бы изменили ее на что-то вроде:

coeffs[i] = (fabs(coeffs[i]) / (coeffs[i] >= 0.0);

Взявабсолютное значение перед делением, бесконечность, которую мы производим для отрицательного, вынуждена быть положительной.В противном случае входные данные начинались с положительного значения, поэтому fabs и деление на 1,0 оставляют значение без изменений.

Производительность

Что касается того, действительно ли это улучшит производительность, то это, вероятно, открыто для многихеще вопрос.На данный момент, давайте посмотрим на код для процессора, так как Godbolt позволяет нам исследовать это довольно легко.

Если мы посмотрим на это:

#include <limits>

double f(double in) {
    return in / (in >= 0.0);
}

double g(double in) { 
    return in > 0.0 ? in : std::numeric_limits<double>::infinity();
}

Итак, давайте посмотрим на полученный коддля первой функции:

  xorpd xmm1, xmm1
  cmplesd xmm1, xmm0
  movsd xmm2, qword ptr [rip + .LCPI0_0] # xmm2 = mem[0],zero
  andpd xmm2, xmm1
  divsd xmm0, xmm2
  ret

Так что это не так уж и страшно - без ответвлений и (в зависимости от используемого процессора) пропускная способность около 8-10 циклов на наиболее разумныхсовременные процессоры.С другой стороны, вот код, созданный для второй функции:

  xorpd xmm1, xmm1
  cmpltsd xmm1, xmm0
  andpd xmm0, xmm1
  movsd xmm2, qword ptr [rip + .LCPI1_0] # xmm2 = mem[0],zero
  andnpd xmm1, xmm2
  orpd xmm0, xmm1
  ret

Это также без веток - и у него нет этой (относительно медленной) инструкции divsd.Опять же, производительность будет варьироваться в зависимости от конкретного процессора, но мы можем, вероятно, планировать, что пропускная способность будет около 6 циклов или около того - не значительно быстрее, чем предыдущий, но, вероятно, по крайней мере на несколько циклов быстреевремени, и почти наверняка никогда не будет медленнее.Короче говоря, это, вероятно, предпочтительнее почти для любого возможного процессора.

Код графического процессора

Графические процессоры, конечно, имеют свои собственные наборы инструкций - но с учетом штрафа, который они несут за ветки, компиляторы для них (и наборы инструкций, которые они предоставляют), вероятно, делают, по крайней мере, столько же, чтобы помочь устранить ветки, что и процессоры, так что есть вероятность, что простой код также будет хорошо работать на нем (хотя, чтобы сказать с уверенностью, вам придется либо исследоватькод, который он произвел или профилировать его).

0 голосов
/ 02 июня 2018

Большой отказ от ответственности: я на самом деле не проверял это, но сомневаюсь, что это действительно быстрее, чем использование троичных.Выполните тесты, чтобы увидеть, действительно ли это оптимизация!

Также: они реализованы / протестированы на C. Они должны быть легко переносимы на GLSL, но вам могут потребоваться явные преобразования типов, которые могут сделатьони (даже) медленнее.

Есть два способа сделать это, в зависимости от того, строго ли вам нужно INFINITY или вы можете просто использовать большое значение.Не используйте ветвящиеся выражения или операторы, но они включают сравнение.Оба используют тот факт, что операторы сравнения в C всегда возвращают либо 0, либо 1.

. Способ на основе INFINITY использует массив из 2 элементов и имеет результат сравнения, выбирающий элемент по выбору.-array:

float chooseCoefs[2] = {0.f, INFINITY}; /* initialize choice-array */
for(uint i = 0; i < 6; i++){
    int neg = coefs[i] < 0; /* outputs 1 or 0 */
    /* set 0-element of choice-array to regular value */
    chooseCoefs[0] = coefs[i]; 
    /* if neg == 0: pick coefs[i], else neg == 1: pick INFINITY */
    coefs[i] = chooseCoefs[neg]; 
}

Если вы можете использовать нормальное (но большое) значение вместо INFINITY, вы можете сделать два умножения и одно сложение вместо:

#define BIGFLOAT 1000.f /* a swimming sasquatch... */
for(uint i = 0; i < 6; i++){
    int neg = coefs[i] < 0;
    /* if neg == 1: 1 * BIGFLOAT + 0 * coefs[i] == BIGFLOAT,
     else neg == 0: 0 * BIGFLOAT + 1 * coefs[i] == coefs[i] */
    coefs[i] = neg * BIGFLOAT + !neg * coefs[i];
}

Опять же, я не сделалЭто не сравнится, но я думаю, что по крайней мере решение на основе массива намного медленнее, чем простые троичные.Не стоит недооценивать силу оптимизации вашего компилятора!

...