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

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

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

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

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

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

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

// All other parts.

Теперь - вот вопрос.

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

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

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

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

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


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

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

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


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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

Во многих случаях обе ветки могут быть вычислены и смешанными по условию в качестве интерполятора. Этот подход работает намного быстрее, чем ветвь. Может также использоваться и для 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));

...

Ответ 4

Здесь настоящий показатель производительности в реальном мире для Kindle Fire:

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

Это работает со скоростью 20 кадров в секунду:

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

Это работает со скоростью 60 кадров в секунду:

gl_FragColor = texture2D ( texture1, TextureCoordOut );   

Ответ 5

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