Про 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 - ровно 4 килобайта и вероятность налететь на алиасинг кэша весьма велика.

После переработки SSE-варианта на такой вот:

            __m128 fpixel = _mm_load_ps(&srowp[col]);
            _ALIGN32_F unsigned int ipix[4] _ALIGN32_E;
            _mm_store_si128((__m128i *)ipix, _mm_cvttps_epi32(fpixel));
            _ALIGN32_F unsigned int opix[4] _ALIGN32_E;
            opix[0] = table[ipix[0]];
            opix[1] = table[ipix[1]];
            opix[2] = table[ipix[2]];
            opix[3] = table[ipix[3]];
            __m128i oreg = _mm_load_si128((const __m128i*)opix);
            _mm_stream_si128((__m128i*)&drowp[col], oreg);

SSE-вариант стал в разы быстрее (все еще в полтора раза медленее, чем AVX, но не в 5), всего-лишь от замены прямой записи в память (через кэш) на запись мимо кэша (понятно что две последние строчки надо слить в одну, для красоты).

Плохая новость заключается в том, что этот код такой простой т.к. это для ЧБ-изображения, вся обработка уже спрятана в LUT. Внедрить такое же в цветном случае будет сложнее, но результат, похоже, того может стоить.
 

P.S. В коде выше, _ALIGN* - это макросы для MSVC и clang, вот такие:

#if defined (WIN32)
#define _ALIGN32_F __declspec(align(32))
#define _ALIGN32_E
#else
#define _ALIGN32_F
#define _ALIGN32_E __attribute__ ((aligned (32)))
#endif

P.P.S. Замена _load_ на _stream_load никакого выигрыша не дает. Добавление явного prefetch на следующий элемент (с проверкой длины, конечно) - дает еще малую толику.

P.P.P.S. Для просто просмотра картинок последовательно - выигрыш не будет виден от слова совсем, декодирование жатых файлов жрет больше в изрядно раз.

 

Comments

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

Там (в FRV) наконец запланирована совсем большая польза от (продвинутой) видеокарты.
Для этого - нужно внутренние потроха полностью переработать. Вот, тружусь.

Насколько могу вспомнить, к этому месту не подходил года два, а все эти два года занимались бантиками, а не основной функциональностью, ради которой программа была сделана (ну, кроме экранного sharpening).

Бантики -- это, конечно, нужно, но вот мне из-за их обилия стало не очень интересно заниматься тестами новых версий, потому что большей частью новых функций я не пользуюсь, а времени вникать во всё это нет. Стало очень много настроек, в которых без мануала уже и не разберёшься (это хорошо, когда каждую гайку по своему динамометру можно затянуть, но гаек реально много). Может, там как-то организовать кнопку Turn On Advanced Options или сделать ярлыки оптовых настроек "под Lightroom", "под С1" и т.п., чтобы новых не пугало количество гаек?

Настроек много, да, но defaults вроде вменяемые.
Может быть надо сделать basic options, либо отдельным диалогом, либо страничкой (первой) в Preferences.

Настройки, по большей части, появляются там, где поведение начинает отклоняться от старых версий - и нужна гайка "чтобы вернуть".

Префетч на СЛЕДУЮЩИЙ элемент — это уже слишком поздно. Надо дальше.

Перед фетчем текущего - я делаю префетч следующего.
+25% перформанса (было 320мс, стало 250)

Дальше копать не стал, потому что на фоне всего остального (распаковка, то-се) выигрыш уже не виден.