Эффективность ветвления в шейдерах

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

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

у меня есть диффузные, нормальные, зеркальные карты для каждой из трех возможных плоскостей отображения и для некоторых граней, которые находятся рядом с пользователем, я также должен применить методы отображения, которые также приносят много текстур (например,parallax occlusion mapping).

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

if (part_actually_needed) {
   perform lookups;
   perform other steps specific for THIS PART;
}

// All other parts.

теперь-вот идет вопрос.

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

на производительность из представленных техника зависит от того, насколько эффективно the ОБОРУДОВАНИЕ НА ОСНОВЕ УСЛОВНОГО Ветвление is выполненный.

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

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

и это даже возможно, что я мог только ухудшить фактическую производительность с ifна основе ветвления?


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

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

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


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

к сожалению, я не уверен, что это утверждение имеет что-то общее с реальной ситуацией...

5 ответов


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

на ЦП, если вы получаете mispredicted филиал, вы создадите трубопровода заподлицо и с конвейеров процессора так глубоко, вы будете эффективно сбросить порядка 20 или более циклов. На GPU все немного по-другому; конвейер, вероятно, будет намного мельче, но нет предсказания ветви, и весь код шейдера будет в быстрой памяти, но это не реальная разница.

трудно узнать точные детали всего, что происходит, потому что nVidia и ATI относительно плотно сжаты, но главное, что графические процессоры сделаны для массового параллельного выполнения. Есть много асинхронные шейдерные ядра, но каждое ядро снова предназначено для запуска нескольких потоков. Я понимаю, что каждое ядро ожидает запуска одной и той же инструкции для всех потоков в любом цикле (nVidia называет эту коллекцию потоков "warp").

в этом случае поток может представлять вершину, элемент геометрии или пиксель/фрагмент, а деформация-это набор из 32 таких элементов. Для пикселей, они могут быть пиксели, которые находятся близко друг к другу на экране. Проблема в том, если в пределах одной основы разные потоки принимают разные решения при условном скачке, основа разошлась и больше не выполняет одну и ту же инструкцию для каждого потока. Аппаратное обеспечение может справиться с этим, но не совсем ясно (по крайней мере для меня), как это происходит. Он также, вероятно, будет обрабатываться немного по-разному для каждого последующего поколения карт. Новейшие, наиболее общие CUDA/compute-shader дружественные nVidias могут иметь лучшую реализацию; старые карты могут иметь более бедные реализация. В худшем случае вы можете найти много потоков, выполняющих обе стороны операторов if/else.

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

также обратите внимание, что операторы if в шейдерах DirectX можно явно пометить как [branch] или [flatten]. Стиль flatten дает вам правильный результат, но всегда выполняет все инструкции. Если вы явно не выбираете один, компилятор может выбрать один для вас-и может выбрать [flatten], что не подходит для вашего примера.

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


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

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


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

...

vec3 c = vec3(1.0, 0.0, 0.0); if (a == b) c = vec3(0.0, 1.0, 0.0);

можно заменить на:

vec3 c = mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), (a == b));

...


вот реальный тест производительности в мире на kindle Fire:

в шейдере фрагментов...

это работает на 20fps:

lowp vec4 a = vec4(0.0, 0.0, 0.0, 0.0);
if (a.r == 0.0)
    gl_FragColor = texture2D ( texture1, TextureCoordOut );   

это работает на 60fps:

gl_FragColor = texture2D ( texture1, TextureCoordOut );   

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