Об эффективном использовании современных CPU

Практика вот к этой презентации:

Берем 36Mpix файлик с D800, распаковываем егонный RAW в 16-битный однокомпонентный битмеп, дальше начинаем процессить.

Процессим без "интерполяции", т.е. 4 пикселя исходного байера образуют один выходной пиксель (режим half_size у LibRaw/dcraw). Получаем такие вот времена:

  1. LibRaw::dcraw_process() плюс формирование RGBA-битмепа: 420ms.
  2. Перепишем этот самый dcraw_process() на SSE3, процессить будем в плавучке (с эмуляцией особенностей dcraw), выдаем такой же 8-битный RGBA: 110ms (и более-менее понятно где еще выиграть миллисекунд 20).
  3. Добавим в предыдущий суп еще: подсчет RAW-гистограммы и сохранение исходного float-битмепа нетронутым (и без дублирования по памяти), т.е. баланс белого и конверсию цвета делаем два раза, один раз для подсчета гистограммы результирующего файла, а второй раз - прямо на вывод. 180ms.
Это все был один поток. Распараллелим его:
  1. "распараллеленный dcraw_process": 130ms (так в RawDigger сделано, тамошний RGB render устроен именно так, но гистограммы и статистика в эти 130ms не входят, равно как и битмеп для показа там готовится иначе и потому дольше).
  2. "распаралеленный ассемблерный dcraw_process": не делал, ожидаю <50ms (потому что вариант с двойным вычислением, как следующий, но без гистограмм - 57ms).
  3. параллельная ассемблерная версия с гистограммами, сохранением float RAW: 75ms
Сравнивая последний вариант с последовательным C-шным, нужно понимать, что в C-шном варианте еще где-то 200ms придется на гистограммы и еще 200 на конверсию int16 - float. То есть реальное ускорение от SSE и параллельной обработки - раз в 10 (75ms против 800). И это оптимизированный C-шный, в LibRaw это место заметно пооптимизировано в сравнении с исходным dcraw.

Минусы: потрачено изрядно времени (несколько дней), окончательного кода 25кб, а промежуточных вариантов было в разы больше. Ну и две баги ловились достаточно мучительно.

Comments

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

Интереснее, где в реале получается бОльшая часть потерь в C-коде.

Особо отвратительные места в LibRaw давно оптимизированы, т.е. там особых каких-то потерь нет. Все размазано тонким слоем.

Просто 16-битные данные не приспособлены к эффективной векторизации.
Вот, к примеру, вычитание уровня черного при работе с плавучкой - это две SSE-команды на пиксель:

pixel = _mm_sub_ps(pixel,black); // вычли
pixel = _mm_max_ps(pixel,zero); // в zero - 4 нуля, если после вычитания получилось отрицательное число, то станет 0

А так же эффективно сделать это же для 16-битного представления - компилятор не может, даже если цикл развернуть и фигачить по два пикселя. Ну, или я неправильными компиляторами пользуюсь.
Про матричное преобразование цвета я полтора года назад написал серию постов:
http://blog.lexa.ru/2011/08/27/o_legacy_i_formatakh_dannykh.html
http://blog.lexa.ru/2011/09/11/o_vektornom_umnozhenii_final.html
http://blog.lexa.ru/2011/09/13/o_vektornom_umnozhenii_vtoroi_final.html
Естественно, я ручками закодировал 6 SSE3-команд, но никакой компилятор так не сделает.

Ну то есть ручной SSE-код быстрее везде, где применимо SSE.
Медленными же остаются а) гистограмма (с которой ничего хорошего сделать нельзя и б) накладывание sRGB-кривой в виде curve[linear-data], с которой возможно и можно что-то сделать, но 75ms на 9 мегапикселей по мне - и так приличный результат.

А, да, распаковка Lossless JPEG D800 занимает ~250ms и что с ней делать - непонятно, там все уперто в "битовый буфер". Т.е. понятно что делать глобально, но не с одним файлом.

>> Ну то есть ручной SSE-код быстрее везде, где применимо SSE.

Во, наверное, тут ключевая фраза. ПМСМ, сгруппировать данные под SSE для компилятора - задача архитрудная.

По счастью, imaging - счастливое исключение, там данные сгруппированы естественным образом. Или почти естественным, каналов в RGB три, а компонентов в векторе - 4.

Ну это не по счастью, ММХ вообще-то под эти задачи и придумывался, насколько я в курсе.

Другое дело, что я не видел, чтобы компиляторы пытались "угадывать" в структурах и операциях над ними соответствующие команды [со]процессора.

Вообще, компилятору сложно распознавать более-менее сложные паттерны. Ну, к примеру, умножение вектора на матрицу. Или даже просто скалярное произведение векторов - я в компиляторном коде ни разу dpps не видел.
Не говоря уже о более сложных паттернах.

я в компиляторном коде ни разу dpps не видел

Использовать DPPS всегда (на любой микроархитектуре) неоптимально.

OK, допустим (НМВ, это зависит от наличия регистров, если их не хватает, то dpps может быть эффективным).

Но ведь и в mulps/haddps компиляторы это не превращают.

Вот-вот.

pixel = _mm_sub_ps(pixel,black); // вычли
pixel = _mm_max_ps(pixel,zero); // в zero - 4 нуля, если после вычитания получилось отрицательное число, то станет 0

А так же эффективно сделать это же для 16-битного представления - компилятор не может, даже если цикл развернуть и фигачить по два пикселя. Ну, или я неправильными компиляторами пользуюсь.

PSUBUSW (aka _mm_subs_epu16) сделает это в одну команду!

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

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

Зачем бы RAW-конвертору повторять процессинг dcraw?

Хочу сказать тебе, что ты гений.

У меня есть два пожелания:
1. Чтобы все были такими же неленивыми как ты, выжымали все соки из CPU, особенно это касается ресурсоёмких приложений, например Adobe, у которых анализ векторов Warp Stabilizer на банальном 1080p работает со скоростью 1fps.

2. Так как первый пункт фантастичен, а ленятся 99% программистов, то хочу чтобы были улучшены компиляторы, усилен АИ в них, чтобы они хотя бы методом перебора пробовали разные версии расширений, SSE, AVX, чтобы добиться такой же эффективности автоматически.

И ещё, планируется ли эти ускорения комиттить в основной код dcraw, который используют тысячи сторонних программ и миллионы пользователей ?

Нет.