Сжатие видеопамяти работает незаметно для программиста, но эффект от него ощутим на каждом кадре: контроллер памяти графического процессора сжимает данные перед записью и распаковывает при чтении, уменьшая объём трафика и эффективно расширяя ёмкость. На кадре с миллионами полигонов и десятками текстур сжатие экономит гигабайты трафика в секунду, и без него современные игры и рендеры не уложились бы в пропускную способность GDDR. На современных GPU сжатие встроено в контроллер памяти и работает прозрачно для приложений, но понимание механики критично для оптимизации: правильная раскладка данных даёт лучший коэффициент сжатия, а неправильная превращает механизм в балласт. Разница между хорошо и плохо сжатыми данными на уровне контроллера достигает двукратной разницы в трафике, и это прямое влияние на FPS в сценариях, упирающихся в полосу памяти.
Разберём типы сжатия в GPU, блоковые форматы, работу с памятью через сжатие, мониторинг и практические приёмы оптимизации. Тема важна для любого, кто пишет рендеры, работает с игровыми движками или занимается машинным обучением на GPU: все три области упираются в полосу памяти, и сжатие прямо влияет на производительность.
Виды сжатия в графических процессорах
Сжатие в GPU делится на две категории: аппаратное без потерь на уровне контроллера памяти и форматы сжатия текстур с потерями. Аппаратное сжатие работает на блоках фиксированного размера, типично 64 или 128 байт, и сжимает данные для снижения трафика между кэшем и памятью. Механизм прозрачен: приложение пишет обычные данные, контроллер сам решает, сжимать или нет, и читатель получает оригинал без всяких действий. Отличие от CPU-сжатия вроде zstd - отсутствие программного управления и минимальная задержка распаковки в один-два такта. CPU-сжатие блокирует поток на время обработки, а GPU-сжатие встроено в конвейер памяти и ничего не блокирует. Именно поэтому GPU-контроллеры сжимают весь трафик, тогда как CPU-сжатие применяют только к большим объёмам. Контроллер обнаруживает паттерны: однородные блоки, градиенты, нулевые области, и кодирует их компактнее, добавляя тег формата в метаданные блока. При чтении контроллер распаковывает блок обратно, и программист не видит разницы. Метаданные хранятся в отдельной структуре рядом с данными, и их размер пропорционален числу блоков, что даёт накладные расходы в районе одного бита на блок.
Форматы текстур, BC1-BC7, ASTC, ETC, сжимают данные с потерями на этапе создания текстуры, и GPU распаковывает их при выборке. Распаковка блочного формата делает шейдерный блок Sampler за один такт, и цена по задержке нулевая по сравнению с чтением несжатых данных. Историческая эволюция шла от простых DXT к BC7 и ASTC, каждый шаг добавлял качество при том же размере. DXT1 появился с палитрой из четырёх цветов, BC7 добавил до 16 цветов с индексами, ASTC предложил адаптивное кодирование. Для современных проектов ASTC на мобильных и BC7 на десктопе дают лучшее качество на пиксель. Эти форматы занимают в памяти в 4-8 раз меньше несжатых RGBA, и их использование критично для больших сцен с тысячами текстур. Аппаратное сжатие контроллера работает поверх, сжимая уже сжатые данные, и коэффициент здесь ниже, но трафик всё равно снижается.
Блоковые форматы и их применение
Формат BC1, исторический DXT1, сжимает RGBA в 4 бита на пиксель: блок 4 на 4 пикселя занимает 8 байт против 64 несжатых. Формат хорош для непрозрачных текстур без плавных градиентов. BC3 добавляет альфа-канал, занимает 1 байт на пиксель, и подходит для текстур с прозрачностью. BC6H для HDR данных с плавающей точкой, BC7 для качественного сжатия RGBA с альфа. Каждый формат имеет специфику: BC1 использует палитру из четырёх цветов, BC7 до 16 цветов с индексами, BC6H формат для диапазона HDR.
ASTC, Adaptive Scalable Texture Compression, предлагает гибкий выбор размера блока от 4 на 4 до 12 на 12 пикселей, и коэффициент сжатия варьируется от 2 до 8 раз. Формат стандарт для мобильных платформ и поддерживается на десктопе. Выбор размера блока это компромисс: больший блок даёт больше сжатие, но худшее качество на деталях. Внутри ASTC кодирует блок через базовый цвет и весовые коэффициенты, подбирая представление под содержимое, что даёт лучшее качество при том же размере, чем фиксированные форматы BC.
На десктопе сжатие текстур управляется API: в Vulkan через формат изображения, в OpenGL через internal format. Драйвер распаковывает данные шейдером при выборке, и программист не делает ничего вручную. Для конвертации готовых текстур используются утилиты вроде texconv от Microsoft, nvtt от NVIDIA, и astcenc от ARM. Конвертация идёт один раз при сборке ресурсов, и в рантайме текстуры уже в блочном формате. Для динамических текстур, обновляемых каждый кадр, выбор между несжатым форматом с хорошим трафиком за счёт контроллера и блочным с конвертацией решается замером на конкретной нагрузке.
Аппаратное сжатие контроллера памяти
Контроллер памяти GPU сжимает данные на пути между кэшем L2 и DRAM. Блок фиксированного размера, обычно 64 байта, анализируется на паттерны: полностью нулевой, однородный, градиент, паттерн с повторениями. Каждый блок кодируется в один из форматов, и в метаданных сохраняется тег. При чтении контроллер восстанавливает оригинал, и кэш L2 видит несжатые данные.
Типичные форматы аппаратного сжатия на NVIDIA GPUs включают ROP-compressed и Delta Color Compression, на AMD - Block Compressed, на Apple - несколько слоёв сжатия для тайлов памяти. Механика везде похожа: анализ паттернов, кодирование компактного представления, тег в метаданных. Разница в деталях: NVIDIA оптимизирует под цветовые буферы и Z-буфер, AMD добавляет общее сжатие для любых данных, Apple работает на тайловой архитектуре. На тайловой архитектуре сжатие работает на тайле в локальной памяти, и трафик внешней памяти снижается радикальнее, чем на немедленном рендеринге: тайл полностью обрабатывается локально, и контроллер сжимает готовый тайл при выгрузке. Мобильные GPU от ARM, Qualcomm и Apple выигрывают от этого вдвойне. Механика везде похожа: анализ паттернов, кодирование компактного представления, тег в метаданных.
Ключевой момент: сжатие работает на блоках фиксированного размера, и если данные в блоке плохо сжимаются, контроллер хранит их без сжатия, добавляя накладные расходы на тег. Правильная раскладка данных, где соседние пиксели в одном блоке, даёт лучший коэффициент, чем случайный разброс. Линейная раскладка с последовательным заполнением сжимается лучше, чем tile-раскладка с чередованием, хотя последняя лучше для выборки в шейдере. Компромисс решается экспериментально на конкретной нагрузке, и инструменты вендоров показывают фактический коэффициент по типам данных.
Очистка памяти и нулевые блоки
Отдельный случай, полностью нулевые блоки. GPU выделяет память через zalloc или приложение пишет нули, и контроллер сжимает такие блоки до бита-тега без данных. Этот механизм даёт максимальное сжатие 64:1, и правильная инициализация буферов нулями вместо оставления мусора экономит и трафик, и эффективную ёмкость. Сценарий на практике: фреймбуфер 4K с 4 цветовых буфера и Z-буфером занимает порядка 64 МБ, и очистка нулями через быстрые пути оставляет всю память в сжатом виде, пока пиксели не будут записаны.
В Vulkan очистка через vkCmdClearAttachments или vkCmdClearColorImage использует быстрые пути контроллера, и GPU обрабатывает очистку огромного фреймбуфера за микросекунды. В OpenGL glClear с теми же гарантиями. Для вычислительных нагрузок в CUDA и ROCm быстрые пути доступны через cudaMemset и hipMemset, которые драйвер распознаёт и переводит в аппаратные операции очистки. Прямая запись нулей через memcpy или шейдер работает медленнее, если драйвер не распознаёт паттерн, и разница на фреймбуфере 4K с 64 МБ достигает сотен микросекунд. В OpenGL glClear с теми же гарантиями. Прямая запись нулей через memcpy или шейдер работает медленнее, если драйвер не распознаёт паттерн.
Мониторинг сжатия и наблюдаемость трафика
Наблюдаемость сжатия зависит от вендора. NVIDIA показывает статистику через Nsight Graphics и nvidia-smi, включая счётчики сжатых и несжатых блоков. AMD даёт детальную телеметрию через Profiler. Общая практика: если трафик памяти ниже теоретического максимума при полной нагрузке, сжатие работает; если трафик близок к максимуму, данные плохо сжимаются, и стоит посмотреть на раскладку. Разбивка по типам трафика показывает, где именно проблема: цветовые буферы, Z-буфер, текстуры или вычислительные данные, и оптимизация направляется туда. Типичная картина для игрового рендера: Z-буфер сжимается лучше всего из-за градиентов глубины, цветовые буферы хуже из-за шума, вычислительные данные почти не сжимаются. Понимание картины подсказывает, какие буферы перемещать в блочный формат, а какие оставить.
Для разработчиков приложений главный рычаг, правильная раскладка данных: текстуры в блочных форматах, рендертаргеты с локальным соседством пикселей, нулевая инициализация неиспользуемых областей. Все три приёма увеличивают коэффициент сжатия контроллера без изменений в коде. Nsight Graphics показывает breakdown по типам: сколько трафика ушло на текстуры, рендертаргеты, и какой процент сжат. Разрез помогает найти худших кандидатов на оптимизацию. Все три приёма увеличивают коэффициент сжатия контроллера без изменений в коде.
Взаимодействие сжатия с виртуализацией GPU
В виртуализированных средах сжатие работает на уровне физического GPU, и гостевые виртуальные машины видят несжатый вид. При раздаче GPU через vGPU статистика сжатия агрегируется на хосте, и мониторинг гостя показывает агрегированные числа. Для облачных провайдеров это означает, что эффективная ёмкость памяти GPU больше физической, и плотность размещения виртуальных машин выше. В средах с MIG физический GPU делится на изолированные инстансы, и сжатие работает независимо в каждом.
Отдельная тема - ZLUDA и аналогичные прослойки, где сжатие работает на уровне программного интерфейса, а не контроллера, и производительность отличается от нативного. Для критичных к производительности задач нативный путь через CUDA или ROCm остаётся предпочтительным. В облачных средах с MIG каждый инстанс получает свою долю сжатия, и статистика агрегируется по инстансам. ZLUDA и аналогичные прослойки полезны для переноса кода CUDA на другое железо, и для задач, где сжатие критично, стоит измерить разницу перед выбором. Для критичных к производительности задач нативный путь через CUDA или ROCm остаётся предпочтительным.
Практический порядок оптимизации сжатия памяти
Оптимизация сжатия памяти GPU укладывается в шесть шагов:
- проверить использование блочных форматов для всех текстур через инструмент анализа ресурсов, заменить несжатые RGBA на BC или ASTC;
- инициализировать буферы нулями вместо оставления мусора, использовать vkCmdClearColorImage или memset для больших областей;
- замерить трафик памяти через Nsight Graphics или AMD Profiler до и после изменений, зафиксировать коэффициент сжатия;
- проанализировать раскладку рендертаргетов: локальное соседство пикселей лучше глобального разброса;
- проверить взаимодействие с Mipmap-генерацией: Mip-цепочки в блочном формате сжимаются хуже, и генерация в несжатом виде с последующей конверсией может быть быстрее; также проверить качество Mip-уровней в блочном формате, где мелкие уровни теряют детали;
- зафиксировать размеры текстур и форматы в профиле ресурсов, добавить проверку в CI на использование несжатых форматов для больших текстур. Регулярный аудит ресурсов ловит деградацию: художники добавляют текстуры, и со временем несжатые данные ползут обратно. Автоматизированный аудит в пайплайне сборки проверяет каждый ресурс на формат и размер, и отклонение от профиля блокирует сборку. Такой подход работает лучше ручных проверок, потому что не зависит от памяти и внимательности команды.
Итог у сжатия памяти GPU двойственный: аппаратное сжатие контроллера работает незаметно и даёт 2-4 раза снижение трафика на типичных данных, блочные форматы текстур уменьшают ёмкость в 4-8 раз, и вместе они превращают ограниченную память GPU в рабочий ресурс для тяжёлых сцен. Ключ к успеху - в осознанном использовании форматов и правильной раскладке данных, а не в надежде на то, что контроллер сам разберётся. Разница между случайной и продуманной раскладкой в трафике памяти достигает двукратной, и в сценариях, упирающихся в полосу, это прямая разница в FPS или во времени итерации обучения.