Ответ 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. Таким образом, вы можете ожидать, что статически однородные ветки будут работать достаточно хорошо. И некоторые аппаратные средства, вероятно, могут обрабатывать динамические ветки так же, как это делает современное настольное оборудование.