Shader optimization: Является ли тройной оператор эквивалентным ветвлению?
Я работаю над вершинным шейдером, в котором я хочу условно удалить некоторые вершины:
float visible = texture(VisibleTexture, index).x;
if (visible > threshold)
gl_Vertex.z = 9999; // send out of frustum
Я знаю, что ветки убивают производительность, когда между соседними данными мало общности. В этом случае каждая другая вершина может получить другое "видимое" значение, что было бы плохо для производительности основного кластера ядра шейдера (из моего понимания).
На мой вопрос: лучше ли тернарный оператор (независимо от вопросов читаемости)?
float visible = texture(VisibleTexture, index).x;
gl_Vertex.z = (visible > threshold) ? 9999 : gl_Vertex.z;
Если нет, преобразует его в расчет, стоящий?
float visible = texture(VisibleTexture, index).x;
visible = sign(visible - threshold) * .5 + .5; // 1=visible, 0=invisible
gl_Vertex.z += 9999 * visible; // original value only for visible
Есть ли еще лучший способ сбросить вершины, не полагаясь на шейдер Geometry?
Заранее благодарим за помощь!
Ответы
Ответ 1
Тернарный оператор - это просто синтаксический сахар для оператора if. Они одинаковы.
Если вам было больше писать внутри вашего оператора if, может быть какая-то оптимизация, которая может быть сделана здесь, но с такой маленькой внутренней частью любой ветки нет ничего действительно оптимизировать.
Часто разветвление по умолчанию не используется.
В вашем случае тернарный оператор (или оператор if), вероятно, сначала оценивает обе стороны условия, а затем отбрасывает ветвь, которая не удовлетворялась условием.
Чтобы использовать ветвление, вам нужно установить флаг компилятора ветвления в вашем шейдерном коде, чтобы сгенерировать сборку, которая поручает графическому процессору фактически попытаться разветкиться (если GPU поддерживает ветвление). В этом случае GPU попытается разветкиться только в том случае, если предсказатель ветвления говорит, что некоторое предопределенное количество ядер будет принимать одну из ветвей.
Ваш пробег может варьироваться от одного компилятора и графического процессора к другому.
Ответ 2
Это математическое решение может быть использовано для замены условных операторов. Это также реализовано в OpenCL как bitselect(condition, falsereturnvalue, truereturnvalue);
int a = in0[i], b = in1[i];
int cmp = a < b; //if TRUE, cmp has all bits 1, if FALSE all bits 0
// & bitwise AND
// | bitwise OR
// ~ flips all bits
out[i] = (a&cmp) | (b&~cmp); //a when TRUE and b when FALSE
Однако я не уверен в том, чтобы реализовать это в вашей ситуации, я не уверен, что полностью понял ваш код, но я надеюсь, что предоставление вам этого ответа поможет или другим.
Ответ 3
На самом деле это зависит от используемого языка шейдеров.
-
В HLSL и Cg троичный оператор никогда не приведет к ветвлению. Вместо этого оба возможных результата всегда оцениваются, а неиспользованный -
отбрасываются. Чтобы привести документацию HLSL:
В отличие от оценки короткого замыкания &, || и?: в C выражения HLSL никогда не замыкают оценку, потому что они являются векторными операциями. Все стороны выражения всегда оцениваются.
Для Cg ситуация аналогична, также здесь тернарный условный оператор является векторным оператором. (документация):
В отличие от C, побочные эффекты в выражениях во втором и третьем операндах всегда выполняются независимо от условия.
-
В ESSL и GLSL тернарный оператор всегда приведет к ветвлению. Это не векторный оператор, поэтому условие должно вычисляться до булева. См. спецификация GLSL:
Он работает на трех выражениях (exp1? exp2: exp3). Эта оператор вычисляет первое выражение, которое должно приводить к скалярному булевому. Если результат верен, выбирает для оценки второго выражения, в противном случае он выбирает для оценки третьего выражения. Только оценивается одно из второго и третьего выражений.
(Источник для ESSL)
Иллюстрация разницы, например, доступна на тестовом сайте Khronos WebGL для тернарных операторов.
Ответ 4
Ответ зависит от трех вещей:
- и какие виды оптимизаций он выполняет.
- архитектура и язык
- точная ситуация, в которой вы используете тернарный оператор.
Рассмотрим следующий пример:
int a = condition ? 100 : 0;
В этом случае типичный компилятор на типичной архитектуре может быть способен исключить ветвь, предполагая, что логические значения представлены как целые числа. Код можно перевести на
int a = condition * 100
Такая же оптимизация возможна с эквивалентным условием if
:
int a = 0;
if (condition) {
a = 100;
}
Все зависит от конкретных оптимизаций, выполняемых компилятором.
Вообще говоря, мой совет: если вы можете использовать тернарный оператор, предпочтительнее его использовать. Скорее всего, он будет оптимизирован компилятором. Это также приводит к более декларативному стилю кода.
Ответ 5
Насколько мне известно, оптимизировать это невозможно. Компилятор по-прежнему будет вынужден разветвляться, каждое из ваших предложений функционально эквивалентно.