CG & C++ blog
56 subscribers
13 photos
2 files
129 links
Краткий обзор публикаций, презентаций, докладов по графике и C++
Download Telegram
Список тэгов

Программирование
#cpp - C++
#cpu_opt - оптимизации для ЦП
#atomics - атомарные операции, работа с кэшем
#threading - многопоточка
#lockfree - Lock free, wait free алгоритмы
#backend - сервера
#ecs - entity component system
#dod - data oriented design, низкоуровневая оптимизация

Графические API, работа на низком уровне
#gpu_opt - оптимизации для ГПУ
#gapi - все графические API
#vulkan #vk - Vulkan API
#metal - Apple Metal
#opengl #gl - OpenGL
#dx - DirectX 11, 12

Рендеринг
#cg - все что связано с компьютерной графикой
#proc_gen - процедурная генерация
#games - обзор технологий из игр

Другое
#news - новости по теме
#blog - новости по моим разработкам
#hw - все что связано с компьютерным железом
#desktop #win #mac #linux - относится к ПК, ноутбуки
#mobile #android #ios - относится к мобильным платфомам
#vr - VR, виртуальная реальность
GDC2018: Shadow of War: performance & memory optimization
Рассказывают про организацию потоков, выделение памяти через large pages, стриминг мипуровней.
Одна из немногих презентация про применение sparse memory.
#cpu_opt #threading #gpu_opt #sparse_mem
PerfTest
Много тестов на производительность разных GPU при чтении разных типов данных.
Например на NVidia последовательное чтение из константного буфера может привести к большим потерям производительности, тогда как на AMD наоборот, это лучше случайного доступа.
Из этого можно сделать вывод, что под каждую модель ГП нужны специфичные оптимизации.
#gpu_opt
Authoring Efficient Shaders for Optimal Mobile Performance
1..8 - описание дебагера и профайлера под Mali GPU и офлайн профайлера шейдеров.
9..16 - как разработчики игр используют тулзы от ARM для оптимизации.
17..19 - детали Valhall архитектуры
20.. - пример оптимизации шейдера
#mali #gpu_opt
Adventures with Deferred Texturing in 'Horizon Forbidden West'

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

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

1..14 - детально разбирают в чем проблема рисования мелких треугольников.
15..19 - для оптимизации мелкой геометрии испоьлзуют visibility buffer.
20..25 - visibility buffer дополнили UV, ddx/ddy и ID материала. Вместо компьют шейдера используют фрагментный, а материал выбирается через тест глубины, где буфер глубины - 16 битный ID матриала.
26..36 - параллельно с рисованием каскадов теней используется асинхронный компьют для наложения матриалов на геометрию из visibility buffer и опять хитрые оптимизации.
37..46 - трансформация вершин сделана на асинхронном компьюте, трансформированные вершины сохраняются во временный буфер и отправлются на растеризацию также в компьют шейдере (слайды 26..36). Геометрия разбита на батчи до 64к треугольников, примерно как мешлеты.
47..68 - дальше идут детали реализации.
69..91 - variable rate shading (VRS) устанавливается для примитивов, что необычно.
#gpu_opt #vis_buf #VRS
Compute versus Hardware
Сравнение растеризации треугольников на фиксиованном конвеере и компьют шейдере (как в Nanite) на разном железе.
Почти на всем современном железе получается быстрее растеризация в компьют шейдере, исключение только Adreno 660.
Интересно что mesh shader оказался медленее компьют шейдера, видимо все упирается в фиксированный конвеер.
#gpu_opt
(video) Metal Compute on MacBook Pro
Различные советы по оптимизации на уровне API и шейдеров.
10:46 - ГП содержит отдельный кэш для буферов и текстур, лучше использовать оба кэша.
15:19 - рекомендуют использовать int вместо uint для обхода массивов.
14:32 .. 22:31 - примеры оптимизации и профилирования.
#gpu_opt #apple_gpu
How to Improve Shader Performance by Resolving LDC Divergence (video)
Пример как пользоваться NSigth для профилирования кадра и шейдеров.
Показывают как замена constant (uniform) буфера на structured (storage) дает ускорение в 1.6 раз.
#nv #gpu_opt
How mesh shaders are implemented in an AMD driver
* У AMD внутри "страые" шейдеры (вершинный/тесселяции/геометрический) преобразуются в примитив шейдер, аналогичный меш шейдерам.
* Каждый поток в меш шейдере может писать в любую вершину и примитив, но для этого выделяется общая память воркгуппы. Этого можно избежать, если каждый поток пишет только в одну вершину и в один примитив.
* hw workgroup size = max(api workgroup size, max vertex count, max primitive count) по этой формуле расчитывается сколько потоков будет использовано.

Task shader driver implementation on AMD HW
* Таск шейдеры выполняются на компьют очереди, а потом графическая очередь выполняет меш шейдеры.
* От использования таск шейдеров есть потеря производительности, поэтому лучше их использовать для больших задач, где потери в драйвере значительно меньше по сравнению с проделанной работой.
#gpu_opt #amd_gpu
Optimizing Compute Shaders for L2 Locality using Thread-Group ID Swizzling
Суть в том, что память текстуры расположенна нелинейна, а в виде Z-curve, morton order и тд.
Поэтому при последовательном доступе начинаются кэшпромахи.

Еще в далеком 2018 я написал прогу, которая за счет хака получает данные текстуры с optimal_layout, в результате чего можно увидеть как идет перестановка данных.
Сейчас обновил код: detect z-curve.

Шаблон перестановки пикселей зависит от производителя и от поколения ГП, но чаще всего минимальный размер тайла 4х4 для 32-битного формата.
#vk #gpu_opt
144FPS Rendering on Mobile: Frame Prediction in 'Arena Breakout'
pdf, video
Разбирают различные способы экстраполяции кадров, аналогично генерации кадров в DLSS и VR.
Наилучшим способом оказалась триангуляция кадра и последующая репроекция.
#gpu_opt
Material Depth Buffer
Техника применяется для отложенного текстурирования в Dawn Engine, где ID материала записывают в 16 битный буфер глубины, а далее рисуется полноэкранные квадраты с разными пайплайнами и тестом глубины equal. ГП умеют это оптимизировать - после теста глубины, прошедшие тест пиксели группируются, чтобы полностью заполнить варп, таким образом 90% ядер ГП загружены.

Сравнение производительности полноэкранного прохода с самым медленным материалом со множеством материалов, если ГП умеет оптимизировать, то падение производительности будет около x2.
NV RTX 2080: 2мс / 5мс.
Mac M1: 2.5мс / 6мс.
AMD RX 570: 5мс / 10мс.
Intel UHD 620: 3мс / 12мс.
Mali G57: 20мс / 26мс.
Adreno 660: 3мс / 30мс, 3 материала - 10мс.

На Adreno для полноэкранного прохода не используется TBDR, возможно из-за этого получилось падение производительности.
#gpu_opt
Неправильная микрооптимизация.

Наткнулся на оптимизированный вариант sqrt и cbrt на shadertoy и конечно же тесты показали падение производительности в 2 раза на sqrt и до 5 раз на cbrt, в том числе на смартфонах 8-ми летней давности.

Почему так произошло: внутри ядра ГП есть отдельные пайплайны, специализированные под разные операции, например FMA (fused multiply add) pipe, SFU (special function unit) pipe - для div,sqrt,log,sin и тд, CVT pipe для конвертации типов.
У многих ГП соотношение 1 SFU на 4 потока, поэтому его использование в 4 раза медленнее FMA, но вызов sqrt только один раз задействует SFU, а "оптимизированный" - 2 раза на деление плюс несколько вызовов FMA, отсюда и потеря производительности.

Возможно ЦП менее заточены под графические задачи и на них деление работает быстрее, чем sqrt, тогда оптимизация имеет смысл.
#gpu_opt
Быстрое чтение и запись больших данных в шейдере

Есть массив данных, которые надо загрузить в шейдер, обработать и записать обратно. Эта задача для GPGPU и на старых рендерилках может решаться нетривиально.
Зато на современной RTX 2080 вызовы imageLoad/Store упирается только в пропускную способность памяти. Но можно ли сделать быстрее?

В Vulkan появился input attachment, который позволяет читать и писать в один и тот же пиксель. Таким способом можно приспособить фрагментный шейдер под задачи GPGPU и это включает сжатие данных, что увеличивает пропускную способность памяти минимум на 10%, а максимум - в 3 раза.
Больше не нужно думать какой там z-curve у текстуры, чтобы перераспределить потоки в компьют шейдере, драйвер сам все сделает.

Способ работает на NV RTX, PowerVR B-Series, Mali Valhall.
На AMD RDNA архитектуре сжатие работает и в компьют шейдере, поэтому такая оптимизация не требуется.

Главный недостаток - графические задачи не параллелятся, поэтому нужно нагружать все ядра.
#gpu_opt
Ветвление в шейдере

Есть 3 способа сделать ветвление в шейдере:
1. Ветви через if, у некоторых есть предубеждение, что это медленно.
2. Выполнить все ветви, умножить их на 0 или 1 и сложить.
3. Заменить множество ветвей на умножение на матрицу.

ГП раскидывает задачи по варпам, где 32 потока выполняют одну инструкцию для 32 значений. Ветвление сделано битовой маской, по одному биту на поток. По очереди выполняются все ветви, но записать значение может только активный поток. Похожий механизм используется и для вспомогательного потока (helper invocation). Но есть и оптимизация - если все потоки варпа идут по одному пути, то выполняется только одна ветвь и производительность улучшается, это называется uniform control flow.

Результаты теста:

На Apple M1, Mali Midgard, Intel 9.5 gen разница между uniform и non-uniform менее 30%, значит плохо работает оптимизация ветвления под uniform control flow.
На AMD GCN4 разница 60%, остальные: Adreno 5xx, Adreno 6xx, Mali Valhall, NV Turing, PowerVR B-series дают более 100%, значит хорошо оптимизируют ветвление.

Более старые ГП лучше справляются с умножением матриц, тот же Mali Midgard использует векторную архитектуру. Поэтому вариант "один раз построить матрицу и умножать на нее" работает быстрее ветвления, но построение матрицы не оптимизировано под вектора и сильно проигрывает при неоднородном выполнении.

В среднем вариант с ветвлением работает быстрее, особенно если заранее не известно будет ли выполняться по одному пути или по разным.
#gpu_opt
Instruction-level parallelism (ILP) - параллелизм на уровне команд.

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

Вместо последовательного выполнения операций над одной переменной оптимальнее выполнять одну инструкцию для 4х переменных, тогда не будет зависимостей по памяти и выполнится максимально быстро.
ILP актуально для SSE/AVX/NEON, а также на ГП начиная с AMD RDNA архитектуры, на NV появилось раньше.

Подробнее на algorithmica, RDNA Architecture, Better Performance at Lower Occupancy.
#gpu_opt #cpu_opt
Instruction-level parallelism на ГП

Для примера возьму NV Turing. Один SM выполняет до 32 варпов, при условии что они умещаются в 64К регистров и используют до 64КБ общей памяти. SM выполняет 64 fp32 FMA операций за цикл, это всего 2 варпа. Инструкция выполняется 2 цикла, поэтому нужно 4 варпа для полной нагрузки SM.

Если есть зависимость между инструкциями, то следующая запускается через 4 цикла, это называется instruction issue latency. Теперь нужно 16 варпов на SM, чтобы не было простоев. При ILP=4 минимальное количество варпов снижается до 4, но увеличивается количество регистров на поток, а значит SM сможет вместить меньше варпов.

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

Также ГП давно перешли на скалярную архитектуру, где операция с float4 это 4 инструкции - по одной на каждый элемент, тут уже ILP=4 и оптимизировать не требуется. Часто код шейдеров достаточно простой, чтобы компилятор смог его оптимизировать и улучшить ILP.

В итоге ILP слабо влияет на производительность ГП. Вот SIMD на ЦП намного более чувствительны к ILP, но об этом позже.
Подробнее: instruction-scheduling, occupancy.
#gpu_opt
Harnessing Wave Intrinsics For Good (And Evil)

Подробно расмотрено несколько алгоритмов с использованием операций над сабгруппой.

Разбирается случай waterfall loop, когда одна операция компилируется в цикл с перебором.
#gpu_opt
Моя статья по Geometry Culling

Обзор различных техник отсечения невидимой геометрии на ПК и мобилках.
На ПК хороший результат показал Visibility Buffer.
Среди мобилок Adreno Low Resolution Z лучше других справляется с неотсортированной геометрией.
#gpu_opt
'Delta Force': Performant High-Quality Terrain and Biome Technology for PC and Mobile
Оптимизация большого открытого мира под ПК и мобилки.
* Фейковый АО для травы и деревьев
* Оптимизация импостеров деревьев
* Процедурная генерация мира. При наложении деревьев проверяют пересечение через воксели.
* Детально разбирают виртуальные текстуры. Для мобилок сжимают в шейдере в ASTC. Проблема на склонах.
* Софтварная тесселяция оказалась в 6 раз быстрее.
#gpu_opt