Избегать ли операторов в DirectX 10 шейдерах?

14

Я слышал, что если в шейдерах следует избегать операторов, потому что обе части операторов будут выполнены, а неправильные будут отброшены (что вредит производительности).

Это все еще проблема в DirectX 10? Кто-то сказал мне, что в этом будет выполнена только правая ветка.

Для иллюстрации у меня есть код:

float y1 = 5; float y2 = 6; float b1 = 2; float b2 = 3;

if(x>0.5){
    x = 10 * y1 + b1;
}else{
    x = 10 * y2 + b2;
}

Есть ли другой способ сделать это быстрее?

Если так, как это сделать?

Обе ветви выглядят одинаково, единственное отличие - это значения «констант» ( y1, y2, b1, b2одинаковые для всех пикселей в Pixel Shader).

PolGraphic
источник
1
Честно говоря, это очень преждевременная оптимизация, просто не меняйте ее, пока вы не сравните свой код и не будете на 100% уверены, что шейдер является узким местом.
pwny

Ответы:

17

Многие правила для микрооптимизирующих шейдеров такие же, как и для традиционных процессоров с векторными расширениями. Вот несколько советов:

  • Есть встроенные тестовые функции ( test, lerp/ mix)
  • добавление двух векторов стоит столько же, сколько добавление двух чисел
  • пьянство бесплатно

Это правда, что на современном оборудовании филиалы дешевле, чем раньше, но все же лучше их избегать, если это возможно. Используя функции Swizzling и Test, вы можете переписать свой шейдер без тестов:

/* y1, y2, b1, b2 */
float4 constants = float4(5, 6, 2, 3);

float2 tmp = 10 * constants.xy + constants.zw;
x = lerp(tmp[1], tmp[0], step(x, 0.5));

Использование stepи lerpявляется очень распространенной идиомой для выбора между двумя значениями.

Сэм Хоцевар
источник
6

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

Вы также должны доверять шейдерному компилятору. Код HLSL, который вы пишете, не должен рассматриваться как прямое представление байт-кода или даже сборки, в которую он будет компилироваться, и компилятор совершенно свободно может преобразовать его во что-то, что эквивалентно, но избегает ветвления (например, иногда может быть lerp). предпочтительное преобразование). С другой стороны, если компилятор определит, что выполнение ветвления на самом деле является более быстрым путем, он скомпилирует его в ветвь. Просмотр сгенерированной сборки в PIX или подобном инструменте может быть очень полезен здесь.

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

Максимус Минимус
источник
4

Цитата из ссылки / статьи, опубликованной Робертом Рухани:

«Коды условий (предикация) используются в более старых архитектурах для эмуляции истинного ветвления. Операторы if-then, скомпилированные для этих архитектур, должны оценивать как взятые, так и не взятые инструкции ветвления для всех фрагментов. Условие ветвления оценивается и устанавливается код условия. инструкции в каждой части ветки должны проверять значение кода условия перед записью результатов в регистры. В результате выводятся только инструкции в взятых ветвях. Таким образом, в этих архитектурах все ветви стоят столько же, сколько обе части ветвление, плюс стоимость оценки состояния ветвления. В таких архитектурах ветвление следует использовать с осторожностью. Графические процессоры серии NVIDIA GeForce FX используют эмуляцию ветки кода условия в своих процессорах фрагментов ».

Как предположил mh01 («Просмотр сгенерированной сборки в PIX или подобном инструменте может быть очень полезным здесь.»), Вы должны использовать инструмент компилятора для проверки вывода. По моему опыту, инструмент Cg от nVidia (Cg по-прежнему широко используется сегодня из-за его кроссплатформенных возможностей) дал прекрасную иллюстрацию поведения, упомянутого в параграфе кодов состояния драгоценных камней GPU (предикация) . Таким образом, независимо от значения триггера, обе ветви оценивались для каждого фрагмента, и только в конце правильная была помещена в выходной реестр. Тем не менее, время вычислений было потрачено впустую. Тогда я думал, что ветвление поможет производительности, особенно потому, что всефрагменты в этом шейдере полагались на единое значение, чтобы выбрать правильную ветвь - это не произошло, как предполагалось. Итак, главное предостережение здесь (например, избегать убершадеров - возможно, самый большой источник разветвляющегося ада).

teodron
источник
2

Если у вас нет проблем с производительностью, это нормально. Стоимость для сравнения с константой все еще чрезвычайно дешева. Вот хорошее прочтение о ветвлении графического процессора: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter34.html

В любом случае, вот фрагмент кода, который будет преформироваться намного хуже, чем оператор if (и гораздо менее читабелен / поддерживаем), но все же избавится от него:

int fx = floor(x);
int y = (fx * y2) + ((1- fx) * y1);
int b = (fx * b2) + ((1 -fx) * b1);

x = 10 * y + b;

Обратите внимание, что я делаю предположение, что x ограничен диапазоном [0, 1]. Это не будет работать, если x> = 2 или x <0.

То, что делает этот фрагмент, конвертирует x в либо 0или, 1и умножает неправильное на 0, а другое на 1.

Роберт Роухани
источник
Поскольку в исходном тесте if(x<0.5)значение для fxдолжно быть round(x)или floor(x + 0.5).
Сам Хочевар
1

Есть несколько инструкций, способных выполнять условия без ветвления;

vec4 when_eq(vec4 x, vec4 y) {
  return 1.0 - abs(sign(x - y));
}

vec4 when_neq(vec4 x, vec4 y) {
  return abs(sign(x - y));
}

vec4 when_gt(vec4 x, vec4 y) {
  return max(sign(x - y), 0.0);
}

vec4 when_lt(vec4 x, vec4 y) {
  return max(sign(y - x), 0.0);
}

vec4 when_ge(vec4 x, vec4 y) {
  return 1.0 - when_lt(x, y);
}

vec4 when_le(vec4 x, vec4 y) {
  return 1.0 - when_gt(x, y);
}

Плюс несколько логических операторов;

vec4 and(vec4 a, vec4 b) {
  return a * b;
}

vec4 or(vec4 a, vec4 b) {
  return min(a + b, 1.0);
}

vec4 xor(vec4 a, vec4 b) {
  return (a + b) % 2.0;
}

vec4 not(vec4 a) {
  return 1.0 - a;
}

источник: http://theorangeduck.com/page/avoiding-shader-conditionals

Алексис Пакес
источник