Обернул шейдер в if, чтобы считать реже. А быстрее не стало
Вы нашли в шейдере тяжёлый кусок — дорогой цикл, лишний проход, ветку с кучей математики — и обернули его в if: мол, для половины пикселей эту дорогую ветку пропустим и сэкономим. Пропустили. А кадр не разгрузился ни на сколько.
Видеокарта прячет задержку памяти тем, что держит не один поток, а тысячи. Один встал на выборке из памяти — планировщик тут же пускает считать другой, у которого данные уже приехали; пока первый ждёт свою порцию, вычислители заняты чужой работой. Вот зачем на GPU тысячи потоков. Мало потоков— прятать нечем, и тогда видеокарта реально встаёт на сотни тактов ожидания.
Но тут и зарыта плата. Одно ядро GPU — это не маленький процессор, а простая и небыстрая линия: ни хитрого предсказания ветвлений, ни глубоких кэшей, один поток на нём медленнее, чем на CPU. Сила — в количестве. И эти линии не независимы: они идут строем, группами (у NVIDIA — по 32), и вся группа исполняет одну и ту же инструкцию разом, каждая над своими данными. Это SIMT — одна команда, много потоков. Представьте не тысячу гонцов, у каждого свой маршрут, а колонну, марширующую в ногу: шаг у всех общий.
Собственно, вот и разгадка вашего if. У этого есть имя — дивергенция ветвлений (branch divergence, ещё говорят «расхождение варпа»). Когда потоки одной группы на развилке расходятся, железу приходится прогнать обе ветки по очереди, отключая на каждой те линии, которых там нет. Группа идёт так же долго, как все пути её потоков вместе взятые. Ваш «пропустить дорогой код» экономит, только если его пропускает вся колонна разом. Один поток свернул в тяжёлую ветку — и остальные тридцать один стоят и ждут его.
По сути, ветвления тут не под запретом — платите вы не за if, а за расхождение внутри группы. Противоположность расхождения — когерентность: это когда соседние потоки заняты одним и тем же, все сворачивают в одну сторону и шагают в ногу. Такая когерентная ветка почти бесплатна — вторая сторона просто не исполняется.
Пример. Материал с enum-режимом и switch (_Mode) в шейдере (классический ubershader, куда свалили все варианты освещения). Если режим один на весь материал — uniform-параметр, — весь варп читает одно значение и идёт одной веткой, остальные просто не исполняются: расхождения нет, дёшево. Но стоит выбирать режим попиксельно — из маски материалов, material-ID в G-буфере, слоёв терреина — и соседние пиксели одного варпа уходят в разные ветки: на стыках материалов варп прогоняет все режимы по очереди.
И та же боль в raymarching и SDF: часть лучей упирается в поверхность за пару шагов, часть марширует до предела — и вся группа топчется в цикле, пока не закончит самый глубокий луч. Ранний выход по одному лучу ничего не даёт: время группы держит самый медленный поток.
В четверг — почему переключение шейдера или материала сбивает весь этот разгон: смена стейта и флаш конвейера, и чем тут спасают батчинг с инстансингом.
#devmath #графика #математика #gpu #simt #оптимизация
Post #123
436

- 🔥 13
- 👍 1