Подтвердить что ты не робот

Замедляют ли условные операторы шейдеры?

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

Лучше использовать это:

vec3 output;
output = input*enable + input2*(1-enable);

вместо этого:

vec3 output;
if(enable == 1)
{
    output = input;
}
else
{
    output = input2;
}

на другом форуме был разговор об этом (2013): http://answers.unity3d.com/info/442688/shader-if-else-performance.html Здесь парни говорят, что операторы If действительно плохо влияют на производительность шейдера.

Также здесь они говорят о том, сколько внутри операторов if/else (2012): https://www.opengl.org/discussion_boards/showthread.php/177762-Performance-alternative-for-if-(-)

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

EDIT:

Что в данном случае, допустим, что enable - это единообразная переменная, и она всегда имеет значение 0:

if(enable == 1) //never happens
{
    output = vec4(0,0,0,0);
}
else  //always happens
{
    output = calcPhong(normal, lightDir);
}

Я думаю, что здесь у нас есть ветвь внутри шейдера, которая замедляет шейдер. Это правильно?

Имеет ли смысл создавать 2 разных шейдера, например, один для другого и другой для части if?

4b9b3361

Ответ 1

Что такого в шейдерах, которые даже потенциально создают проблемы производительности операторов if? Это связано с тем, как выполняются шейдеры и откуда графические процессоры получают свою огромную вычислительную производительность.

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

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

Если только это не так.

Например, если условие является тем, которое принимается каждым вызовом в волновом фронте, то расхождение во время выполнения не требуется. Таким образом, стоимость if - это просто стоимость проверки условия.

Итак, допустим, у вас есть условная ветвь, и предположим, что все вызовы в волновом фронте будут иметь одну и ту же ветвь. Существует три варианта характера выражения в этом состоянии:

  • Статическое время компиляции. Условное выражение полностью основано на константах времени компиляции. Если вы посмотрите на код и знаете, какие ветки будут взяты. Практически любой компилятор обрабатывает это как часть базовой оптимизации.
  • Статически равномерное ветвление. Условие основано на выражениях, включающих вещи, которые во время компиляции известны как постоянные (в частности, константы и значения uniform). Но значение выражения не будет известно во время компиляции. Таким образом, компилятор может быть статически уверен, что волновые фронты никогда не будут нарушены этим if, но компилятор не может знать, какая ветвь будет взята.
  • Динамическое ветвление. Условное выражение содержит термины, отличные от констант и униформ. Здесь компилятор не может априори сказать, будет ли волновой фронт разбит или нет. Должно ли это произойти, зависит от оценки времени выполнения выражения условия.

Разные аппаратные средства могут обрабатывать разные типы ветвления без расхождения.

Кроме того, даже если условие принимается разными волновыми фронтами, компилятор может реструктурировать код, чтобы не требовать фактического ветвления. Вы привели прекрасный пример: output = input*enable + input2*(1-enable); функционально эквивалентен оператору if. Компилятор может обнаружить, что if используется для установки переменной, и, таким образом, выполнить обе стороны. Это часто делается для случаев динамических условий, когда тела ветвей маленькие.

Практически все оборудование может обрабатывать var = bool ? val1 : val2 без необходимости расходиться. Это было возможно еще в 2002 году.

Поскольку это очень зависит от оборудования, это... зависит от оборудования. Однако существуют определенные эпохи аппаратного обеспечения, на которые можно посмотреть:

Рабочий стол, Pre-D3D10

Там это своего рода дикий запад. Компилятор NVIDIA для такого оборудования был известен тем, что обнаруживал такие условия и фактически перекомпилировал ваш шейдер всякий раз, когда вы меняли униформу, которая влияла на такие условия.

В целом, в эту эпоху происходит около 80% "никогда не использовать if операторов". Но даже здесь это не обязательно так.

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

Компиляторы этой эпохи делают все возможное, чтобы оптимизировать шейдеры, чтобы простые условия могли быть просто выполнены. Например, ваш output = input*enable + input2*(1-enable); - это то, что приличный компилятор может сгенерировать из вашего эквивалентного оператора if.

Рабочий стол, Post-D3D10

Аппаратные средства этой эпохи обычно способны обрабатывать статически однородные операторы ветвления с небольшим замедлением. При динамическом ветвлении вы можете столкнуться с замедлением или не столкнуться с ним.

Рабочий стол, D3D11+

Аппаратные средства этой эпохи в значительной степени гарантированно способны обрабатывать динамически однородные условия с небольшими проблемами с производительностью. Действительно, он даже не должен быть динамически однородным; до тех пор, пока все вызовы в одном и том же волновом фронте идут по одному и тому же пути, значительных потерь производительности не будет.

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

Mobile, ES 2.0

Добро пожаловать обратно на дикий запад. Хотя в отличие от настольного компьютера Pre-D3D10, это происходит главным образом из-за огромного различия аппаратного обеспечения ES 2.0. Там такое огромное количество вещей, которые могут справиться с ES 2.0, и все они работают очень по-разному друг от друга.

Статическое ветвление, вероятно, будет оптимизировано. Но получите ли вы хорошую производительность от статически равномерного ветвления, очень зависит от аппаратного обеспечения.

Mobile, ES 3. 0+

Аппаратное обеспечение здесь более зрелое и способное, чем ES 2.0. Таким образом, вы можете ожидать, что статически однородные ветки будут работать достаточно хорошо. И некоторые аппаратные средства, вероятно, могут обрабатывать динамические ветки так же, как это делает современное настольное оборудование.

Ответ 2

Он сильно зависит от оборудования и условий.

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

В этом последнем случае вам в значительной степени придется тестировать и тестировать, потому что это будет зависеть от сложности кода в каждой ветки и как "согласовано" решение ветки.

Например, если одна из ветвей взята в 99% случаев и отбрасывает фрагмент, то, скорее всего, вы хотите сохранить условный. Но OTOH в вашем простом примере выше, если enable - какое-то динамическое условие, выбор арифметики может быть лучше.

Если у вас нет случая с четким вырезом, как указано выше, или если вы не оптимизируете для одной фиксированной известной архитектуры, вам, вероятно, лучше не соответствовать фигуре компилятора, которая вам нужна.