Эффективность ветвления в шейдерах - PullRequest
36 голосов
/ 14 ноября 2010

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

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

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

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

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

// All other parts.

Теперь - здесь возникает вопрос.

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

Производительность изпредставленный метод зависит от того, насколько эффективно реализовано аппаратное обеспечение на основе аппаратных средств *1037*.

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

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

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


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

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

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


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

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

Ответы [ 5 ]

30 голосов
/ 14 ноября 2010

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

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

30 голосов
/ 14 ноября 2010

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

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

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

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

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

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

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

25 голосов
/ 06 октября 2014

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

...

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));

...

10 голосов
/ 12 февраля 2013

Вот реальный тест производительности на разжигании 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 );   
7 голосов
/ 14 ноября 2010

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

...