AVX

AVX2 speedup

На одном и том же CPU (i7-7700k на базовых частотах), переход с SSE3 на AVX2 дает выигрыш, если по отдельным кускам:

  • Half-демозаика + ББ + цветовая конверсия + тоновая кривая, запись RGB bitmap: 1.97/1.11 sec (SSE/AVX)
  • Half-демозаика + ББ + цветовая конверсия + построение гистограммы: 2.11/1.483
  • Чтение raw-данных (int16), преборазование в float, вычитание черного, построение RAW-гистограммы: 1.78/1.72. Гистограмма - это очень медленно.....

Тестовый набор: 22 файла Sony A7RM2, т.е. ~900Mpix в сумме. Чиселки - CPU time (не wall time, потому что мультитрединг).

Общий выигрыш на всю программу - не больше 10 процентов, потому что уперлись теперь в видеодрайвер (генерация мипмепов, загрузка текстур в видеокарту), все спинлоки - тама.  Надо более продвинутый OpenGL использовать.

 

Про AVX2 и размывание кэшей

Вот наконец удалось ощутить офигенную пользу от AVX2, причем двойную. Вот такой вот код:

_mm256_stream_si256((__m256i *)&drowp[col], _mm256_i32gather_epi32((const int*)table, _mm256_cvttps_epi32(_mm256_load_ps(&srowp[col])), 4));

в 4.5-5 раз быстрее, чем простой SSE2 аналог (в котором, понятно, нет gather) и в ~6 раз быстрее скалярного C-кода:

drowp[col] = table[(unsigned)srowp[col]];

Рассмотрение всего хозяйства под микроскопом показало, что основной взнос в результат дает _mm256_stream, а вовсе не gather. Стоит заменить stream на store, как все сразу портится. По достаточно очевидной причине: длина строки и drowp и srowp -...

AVX(2) data load

Граждане, что-то вот отчаялся понять, отчего может быть так что

Вот так - хорошо, профайлер в этом месте особых тормозов не показывает:

                __m128 p0 = _mm_loadu_ps(source);
                __m128 p1 = _mm_loadu_ps(source + 4);
                __m128 p2 = _mm_loadu_ps(source + 8);

 

А вот так - нехорошо:

                __m256 i0008 = _mm256_loadu_ps(fsrcstart3);
                __m256 i0915 = _mm256_loadu_ps(fsrcstart3 + 8);
                __m256 i1623
...

AVX2 и VPGATHERDD - продолжение

Продолжаем дневник программиста на AVX2 (начало и продолжение).

Добрался до 4-го места, которое хотелось пооптимизировать окончательно уже года два как: берем 16-битные целые (RAW-данные), конвертируем в плавучку, вычитаем черный, результат записываем. То есть первый этап дебайеризации.

Параллельно считаем RAW-гистограмму и карту пересвета-недосвета (на самом деле там карта экспозиции считается, а не просто пересвет-недосвет, а дальше она визуализируется с лимитами, все что выше/ниже лимита раскрашивается.).

Вот в этом месте VPGATHERDD (из RAW-значений в экспозицию по LUT) внезапно дал выигрыш...

Еще про AVX2 и VPGATHERDD

Продолжение вот к этому вот тексту и к тамошним комментариям:

1. Собрался с духом и переписал тройку мест на 256-битные команды. Помогло: время исполнения этих кусочков упало на 15-25%. Буду дальше писать.

Это без особой оптимизации, старый код у меня SSE3, никаких BLENDPS/EXTRACTPS не использовалось, соответственно и AVX2 - это чисто замена _mm_ на _mm256_ ну и типы 256-битные. В уже переписанных кусках буду еще смотреть на DPPS: на SandyBridge эта инструкция выигрыша не давала, но может быть поможет на Haswell+.

2. А вот с VPGATHERDD - сплошное разочарование:

  • Для гистограммы (когда gather, потом инкремент, потом поэлементная запись) - получается серьезный проигрыш в производительности, а код сложнее.
  • Для операций вида bitmap[index][channel] = contrast_curve[value] проигрыш тоже есть, хотя и незначительный. Совсем оптимизированное (читаем только 6 элементов, для альфа-канала берем defaults т.е. через masked load) - совсем незначительно медленнее, единицы процентов на всю функцию, но медленнее.

Уж не знаю, кому эта VPGATHERDD в таком виде нужна. Ну разве только на новых процессорах она побыстрее.

Про AVX2

Давненько я в этом блоге на ассемблере не писал.

Вот значит кусочек FRV, который накладывает выходную тоновую кривую. Точнее, два кусочка, один на старом добром SSE2 (которого там ровно одна команда, сконвертировать float-int), а второй - на новом модном AVX2, правда 128-битном, но зато с table lookup.

#ifndef USE_AVX
        uint32_t __declspec(align(16)) dpix[4];
        _mm_store_si128((__m128i *)dpix, _mm_cvttps_epi32(pixel));
        dest[col] = 0xff000000 | curve[dpix[2]] <<
...

Презентация с Highload

По просьбам трудящихся масс, моя презентация с Хайлоада:

Никаких откровений нет, задача была - показать что multicore/simd - это очень просто и стоит того. Читатели моего уютненького легко узнают примеры 1.5-2-летней давности.

Анимированность Slideshare порезала, но вроде накрывающих друг друга картинок у меня нет.

ISPC 1.10

ISPCшные примеры, понятно что подобранные со вкусом, дают повод задуматься:

C:\> deferred_shading.exe pp1920x1200.bin

[ispc static + tasks]:		[151.426] million cycles to render 1920 x 1200 image
[C++ serial dynamic, 1 core]:	[2883.776] million cycles to render image
				(19.04x speedup from ISPC)

C:\>mandelbrot_tasks.exe 
[mandelbrot ispc+tasks]:	[190.607] million cycles
[mandelbrot serial]:		[2133.784] millon cycles
				(11.19x speedup from ISPC)
C:\> mandelbrot.exe
[mandelbrot ispc]:		[102.830] million cycles
[mandelbrot serial]:		[276.757] millon cycles
				(2.69x speedup from ISPC)

Про AVX и ISPC

Разработчики Intel SPMD Program Compiler, который в этом блоге уже несколько раз поминался, выпустили версию 1.0.9 в которой

=== v1.0.9 === (26 September 2011)

The binary release of v1.0.9 is the first that supports AVX code generation. Two targets are provided: "avx", which runs with a programCount of 8, and "avx-x2" which runs 16 program instances simultaneously.

Честь им за это и хвала, мои попытки (не слишком настойчивые) самостоятельно собрать ISPC с LLVM3 так и не увенчались успехом, то какие-то ошибки в LLVM-овских H-файлах, то не линкуется, не больно то и хотелось.

Так как предыдущие тесты никуда не делись, я их переделал с новым ISPC, как с SSE4, так и с AVX.

О векторном умножении: нет гигапикселя в секунду

Тема матричного цветопреобразования не отпускает нас.

Наш читатель maratyszcza намекает нам, что haddps - тоже хорошая инструкция.

Его вариант вы найдете по вышеуказанной ссылке, а я потестировал вариант попроще, без unroll, без префетча, без одновременной обработки половинок данных. Исключительно для совместимости с прошлыми тестами:

О векторном умножении - финал

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

Ассемблерные упражнения на тему векторных расширений.

О компиляторах и процессорах: AVX

Армянское радио Нас спрашивают:

Как измениться производительность intrinsic варианта на Core-i7, если поменять
_mm_dp_ps на _mm256_dp_ps
_mm_blend_ps на _mm_blend256_ps

То-есть насколько вырастить производительность если мы совсем на AVX переедем и будет обрабатывать по 8 float за проход? А то слухи разные ходят... от 0% до 200% роста.

Отвечаем:

О компиляторах и процессорах

В комментариях к моему посту о целых числах и плавающей точке мне посоветовали обратить внимание на векторные типы данных и сравнить их по производительности со скалярными типами (ну и ручным SSE-ассемблером тоже).

Я обратил и сравнил, но в процессе получилась масса побочных результатов (разные архитектуры, разные компиляторы), которые жалко выкинуть, а хочется опубликовать.

Об автоматической векторизации

Провел на поминавшемся вчера ISPC еще один тест, на применимость ровно в том месте, куда он лучше всего приспособлен.

Есть такое ужасное место в обработке изображений (уже поминавшееся в этом блоге): преобразование из линейной гаммы в sRGB-гамму или в Lab. Там в формуле сначала линейный участок, а потом степенной. Вот как это выглядит, если делать в лоб для плавающей точки:

void linear2srgb(float *in, float *out)
{
   for(int i = 0; i< DATA_SIZE; i++)
        out[i] = ((in[i]<=0.0031308f)? 12.92f*in[i] : (1+0.055f)*powf(in[i],1/2.4f)-0.055f);
}

Ну, понятно, для (целочисленных) 8-16-битных данных составляют лукап-таблицу, а дальше фигачат по ней, а вот для большей битности таблица получается изрядно большой. Вычислять эту штуку в лоб мешают две вещи:

  • ветвление на каждое входное данное;
  • возведение в степень, которое тоже очень медленное: на SSE/AVX такой функции нет, на FP87 есть, но безобразно медленная.
К примеру, при обработке плавающих данных LCMS (преобразование в Lab, преобразование по матричному профилю в sRGB) процентов 90 времени уходит именно на вышепоказанную операцию (правда в LCMS это место еще сделано потрясающе неудачно с точки зрения производительности).

Как я уже писал, правильное решение заключается в замене вышепоказанной функции чем-то приличным, скажем для кубических сплайнов с таблицей в 4к строк максимальная ошибка по всему диапазону не превышает 10-6, что для всех применений достаточно, при скорости порядка 1.2-1.5Gb/sec на одно процессорное ядро. Но одна строчка кода превращается в несколько десятков, таблицу коэффициентов сплайнов надо еще построить, что мучительно.

Посмотрим, что можно сделать с помощью ISPC и можно ли вообще что-то.

Subscribe to AVX