Сегодня проводил переработку старой версии “сохранялки скриншотов”. Задача в общем-то простая: есть картинка в формата DXGI_FORMAT_R10G10B10A2_UNORM, нужно сохранить её в *.bmp файл. Естественно по мимо того, что строки в bmp идут снизу вверх, он знать не хочет не про какой формат DXGI_FORMAT_R10G10B10A2_UNORM и его требуется конвертировать в B8G8R8 (да-да, 3 байта на пиксель ещё и задом на перёд).
Беглый осмотр старой реализации выявил обработку по 4 пикселя (по 2 пикселя на регистр), с записью результата двумя операциями: MOV [R9], RCX; MOV [R9+8], EDX;
. Казалось бы всё неплохо (для картинки 640х448 операция преобразования из региона источника в регион приёмник занимала ~1.6 млн. тактов), но вот что-то дёрнуло меня переписать сие чудо на SSE.
Т.к. операция довольно простая + хорошо векторизуется то следовало ожидать приличного прироста производительности. Сразу задумался, какие операции на обрабатываемыми данными мне нужны помимо чтения и записи. Подумав, пришел к мысли что: битовые сдвиги (нужно как-то доставать верхние 8 бит из 10-ти битных значений), смешивание (т.к. для извлечения разных цветовых компонент требуются сдвиги на разные величины) и перестановка байтов (имеет RGBARGBARGBA, а нужно то BGRBGRBGRBGRxxxx).
Тут на помощь пришли инструкции: PSRLD (SSE2), PBLENDVB (SSE4.1) и PSHUFB (SSSE3). Во первых время выполнения сократилось до ~1.2млн. тактов (-27.5%), во вторых размер кода снизился раза в 2. Для сравнения стоит сказать что простое копирование региона памяти размеров 1120Кб (640*448*4) занимает ~1.15 млн. тактов, т.е. преобразование на скорости копирования памяти – отлично. В общем результатом переписывание стало сокращение времени снятия скриншота 2560х1600 на 1.25мс.
Правда не обошлось и без очередной ложки дёгтя: попытка использовать WC-регион памяти привела только к падению производительности в почти 4 раза. В очередной раз ловлю себя на мысли, что знаю далеко не всё об особенностях PC-архитектуры.
P.S. Для сохранения в *.bmp DXGI_FORMAT_R8G8B8A8_UNORM достаточно только инструкции PSHUFB, т.е SSSE3. Ах да, и еще: реализация этой операции на паскале занимает 6.6млн тактов (хотя стоит признать, что реализация не самая оптимальная – можно и быстрее).