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

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

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

SSE2-код

Сразу оговорка про Core2: быстродействие всех рассмотренных ниже сниппетов на этом процессоре практически одинаково, судя по всему все уперлось в память. А вот разница на Core i7-AVX довольно существенна.

Код, аналогичный тому, что делает хороший компилятор из векторного кода.

void dotp_sse2_load1 (float q[], int sz)
{
        int i;
        __m128 m0 = _mm_load_ps(matT[0]);
        __m128 m1 = _mm_load_ps(matT[1]);
        __m128 m2 = _mm_load_ps(matT[2]);
        __m128 m3 = _mm_load_ps(matT[3]);
        for(i=0;i<sz;i++)
        {
//1 начало чтения данных
                __m128 x0 = _mm_load_ps1(&q[i*4]);
                __m128 x1 = _mm_load_ps1(&q[i*4+1]);
                __m128 x2 = _mm_load_ps1(&q[i*4+2]);
                __m128 x3 = _mm_load_ps1(&q[i*4+3]);
//2 все прочитано
                __m128 r0 = _mm_mul_ps(x0,m0);
                __m128 r1 = _mm_mul_ps(x1,m1);
                __m128 r2 = _mm_mul_ps(x2,m2);
                __m128 r3 = _mm_mul_ps(x3,m3);
                __m128 t1 = _mm_add_ps(r0,r1);
                __m128 t2 = _mm_add_ps(r2,r3);
                __m128 t3 = _mm_add_ps(t1,t2); 
                _mm_store_ps(&q[i*4],t3);
        }
};
}

Каждый load_ps1 превращается в movss + shuffle, что полностью аналогично тому коду, который сделал clang. На Core i7-AVX @4.5Ghz получается 570 Mpix/sec, что несколько быстрее, чем скалярное умножение командой процессора (SSE4.1), при том что код остается SSE2.

Можно сделать лучше (только кусок от //1 до //2 в предыдущем сниппете):

//1
                __m128 x0 = _mm_load_ps(&q[i*4]);
                __m128 x1 = x0;
                x1 = _mm_shuffle_ps(x1,x1,0x55);
                __m128 x2 = x0;
                x2 = _mm_shuffle_ps(x2,x2,0xAA);
                __m128 x3 = x0;
                x3 = _mm_shuffle_ps(x3,x3,0xff);
                x0 = _mm_shuffle_ps(x0,x0,0);
//2
1 load, те же 4 shuffle, три присваивания. Получаем: 616 Mpix/sec, что уже почти дотянулось до AVX-скалярного умножения, где без prefetch было 626Mpix/sec. А 128-битный SSE4.1 вариант - обогнали и сильно (там было 558 Mpix/sec).

AVX-варианты

Раз сравниваем с 256-битным vdpps, попробуем и другие варианты.

Вариант с 8-ю movss в цикле (аналог sse_load1) оказался медленнее SSE2-варианта: не хватает регистров и используются промежуточное сохранение на стеке.

256-битный вариант с _mm_broadcast_ss: то же быстродействие, что у 128-битного варианта (в пределах разброса результатов).

Вариант с одним 256-битным load:

void shuffle256 (float q[], int sz)
{
        int i;

        __m256 m0 = _mm256_load_ps(matTX2[0]);
        __m256 m1 = _mm256_load_ps(matTX2[1]);
        __m256 m2 = _mm256_load_ps(matTX2[2]);
        __m256 m3 = _mm256_load_ps(matTX2[3]);


        for(i=0;i<sz;i+=2)
        {
                __m256 x0 = _mm256_load_ps(&q[i*4]);
                __m256 x1 = x0;
                x1 = _mm256_shuffle_ps(x1,x1,0x55);
                __m256 x2 = x0;
                x2 = _mm256_shuffle_ps(x2,x2,0xAA);
                __m256 x3 = x0;
#ifndef NO_PREFETCH
                _mm_prefetch((const char*)&q[i*4+8],_MM_HINT_NTA);
#endif
                x3 = _mm256_shuffle_ps(x3,x3,0xff);
                x0 = _mm256_shuffle_ps(x0,x0,0);
                __m256 r0 = _mm256_mul_ps(x0,m0);
                __m256 r1 = _mm256_mul_ps(x1,m1);
                __m256 r2 = _mm256_mul_ps(x2,m2);
                __m256 r3 = _mm256_mul_ps(x3,m3);
                __m256 t1 = _mm256_add_ps(r0,r1);
                __m256 t2 = _mm256_add_ps(r2,r3);
                __m256 t3 = _mm256_add_ps(t1,t2);
                _mm256_store_ps(&q[i*4],t3);
        }

};
683 Mpix/sec без _mm_prefetch и 700 Mpix/sec с prefetch. 700 - это 11.2Gb/sec, вполне такой вкусный результат. Кстати, это 6.5 клоков на пиксель или примерно 1.5-2 (как считать, по входу или по выходу) процессорных клока на компонент. Гм.

Мораль

Аппаратная поддержка не означает счастья, то же самое скалярное умножение, реализованное вручную (но ценой расхода регистров) оказывается быстрее специализированной инструкции. Непринципиально, около 10% выигрыша, причем как на AVX (256-битные операции), так и на SSE. Да, конечно, у нас не просто два вектора, а матрица на вектор, но разница - в несколько blend.

При этом, 128-битная "эмуляция" SSE4.1-инструкции - требует всего-лишь SSE2, что делает ее еще полезнее.

Comments

А у меня общий вопрос: Вы ведь в курсе, что Win8 будет работать и на ARM? А там нет SSE инструкций. Зато есть OpenCL. И на x86 есть OpenCL.

Для Алексовых задач ARM явно не приспособлен. Вот Cell мог бы, но...

А что за задачи?

Дак Вы тред-то почитайте, даже ссылки дадены.

Обработка (в том числе потоковая) многопиксельных тяжёлых изображений (условно говоря, 20+ Mpix при 3*16 bit/pixel)

Лёха, я не очень твой таргет переврал? ;)

А, ну про это я в курсе. Изображения интересно было бы обрабатывать и на ARM-ах.

Интересно, да. Но эта обработка (если мы говорим о носимом гаджете) должна в такоем случае, по-хорошему, занимать не более 100-200 мс на кадр. То есть, для кадра в 20 Mpix должна быть производительность по крайней мере в 100-200 Mpix/s, что для арма с его контроллером памяти мне кажется (по крайней мере пока) труднодостижимым.

Так как там экранчик отсилы в мегапиксель, то больше оного мегапикселя и обрабатывать не надо. А откуда он взялся, это зум куска кадра или уменьшение существующего - несущественно.

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

ARM - это же (пока) телефоны и планшеты?

Ну, iPad/Galaxy Tab image processing мог бы быть привлекательным, например

В теории.

А на практике там (в ойпаде) такой геморой с получением RAW-файлов с камеры..... Есть Camera Connection Kit, SDK к нему я летом не нашел и наплевал.

Ну это в ойпаде. Рынок вот только что начал заваливаться уже более-менее сносными 10" планшетами на honeycomb. У некоторых есть usb-host. Я вот уже думаю о том, чтобы в отпуск брать что-то типа asus transformer, у которого есит нормальный клавиатурный док с батарейкой, несколько нормальных usb-портов и usb-host. Софт для первичной сортировки RAW'ов на более-менее сносном экране тут бы вполне сгодился. Не то, чтобы очень-очень надо, но бывают моменты когда делать всё равно нечего - почему бы и не?

А вы точно хотите их первично сортировать именно как RAW, а не по JPEG-версиям?

У меня сомнения на эту тему есть, да. На ARM...

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

Но это все очень "пока".

У меня целевая платформа для процессинга RAW - мощный десктопный PC и/или мощный ноут.

OpenCL - хорошо, но пока политика - оптимизировать хот-споты, а не переписывать весь код.

На ARM есть WMMX и NEON

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

А откуда это требование взялось, если не секрет? "Мощный PC или ноут"?

Ну грубо говоря оттуда, что у (профессионального) фотографа это будет маленькая часть общего бюджета.

Ну т.е. не от хорошей жизни требование такое, да? И тот же фотограф с большим удовольствием возил бы с собой легкий тонкий нетбук или планшет, а не 3-килограмовый ноутбук?

On location (где нетбук или планшет) задачи другие. На нетбуке-планшете просто нет места для финальной обработки съемки.
То есть там достаточно "95% качества" при приоритете скорости.

А при постпроцессинге нужно "100% качества", многие даже мирятся с непроизводительными ожиданиями в первые секунды - и эти то секунды я и хотел бы убить.

Ну тогда да, еще долгое время самые мощные и распространенные стационарные и мобильные компы будут на x86 архитектуре.

Но вот что меня гложет: В том же AMD E-350, который 18 ватт ест, встроенный GPU - 80 гигафлопс. Это немало.

Трехкилограммовый-то зачем. У меня ноутбук с i5, 8Gb памяти, рейдом из SSD и великолепным дисплеем 13" весит всего кило двести (или триста, не помню).

А модель какая?

Sony Z. Не та, которая только что вышла, а предыдущая, Z1 она называется что-ли.

Уверен, эти инструкции и используются в OpenCL реализации.

Хы :) Иронию понял.

Ну так там, хочется надеяться, и память общая у CPU и OpenCL, т.е. можно будет опять оптимизировать хот-споты.

Это да. Я вспомнил наш недавний разговор, и теперь я вижу, что для Ваших задач APU действительно могут оказаться очень интересны.

Ага, у меня чешутся руки купить что-то на AMD-шном APU, но пока не вижу микро-платок (mini-ATX) под него, а micro-ATX поставить некуда.

Тьфу. Micro-ATX поставить некуда, а mini-ITX не вижу в продаже.

Не надо гнать в ЖЖ в 7 утра спросонья.

Да, такие есть, но они какие-то совсем жалкие, как мне кажется.

Я хочу старшую модель, которая A-. Если в mini-ITX успешно суют Core2 и прочие Core i5, то и APU этот должен бы залезть.

Да, прирост производительности на AVX меньше 15%. И это при том что там легко наступить на грабли и получить performance degradation если какая-нибудь либа вставить SSE инструкцию.

Одно радует, что можно хоть получить заметный прирост производительности за счёт быстрой памяти и высокой частоты проца. Думаю на твоей задаче, которая O(n), скорость памяти важней всего. Интересно посмотреть действительно ли поднятие частоты памяти дает прирост.
Так же в свое время я пытался понять, как писать код чтобы воспользоватся преимуществами Dual-channel и Triple-channel памяти, но прироста не наблюдал... В системнике то вставлял планки, то вытаскивал.

Я так для себя понял, что AVX - это тот же dual issue SSE, только поставленный в ряд.

Вместе с тем, если генерировать SSE-код из тех же макросов (а не AVX), то скорость получается заметно ниже и начинает зависеть от компилятора. Скажем, для _load1 без префетча и компилятора intel - 316Mpix вместо 527 (c префетчем - возвращается).

Т.е. граблей тут густо разложено.

А оно реально надо? Ну то есть 500 vs 600 vs 700? Все одно, через года через полтора-три оно удвоится/учетверится, в кэш влезет вообще вся картинка и оно достигнет статуса "да пофиг, все достаточно быстро".

PS: Вспоминая mp3/mpeg1 на 486, где декодер оного действительно в проц упирался, но с появлением P/PII проблема рассосалась сама собой.

Сейчас "пофиг, достаточно быстро" (т.е. ~25 кадров/сек) достигается только для экранного разрешения в 2-4 мегапикселя и без качественной демозаики. А с демозаикой - в лучшем случае окошко "100%" размером 500x500.

То есть *сейчас*, если не ждать несколько лет, делать таки что-то надо.

А упражнения ради очередных 50 или 100Mpix/sec - это, конечно, чисто для воскресного вечера.

Не говоря о том, что не у всех юзеров Core i7 @4.5Ghz, а если целиться еще и в машину 4-летней давности, как мой ноут (Core2 T7500), то там еще чуть не на порядок все медленнее.

Я вот почесал репу еще.
Что-то у меня сомнения, что оно удвоится-учетверится на одном потоке на существующей однопоточной codebase.

Так это, а у твоей целевой аудитории разве не по 4+ ядра в тачке стоит? То есть достаточно, чтобы один поток мог использовать где-то четверть псп памяти и дальше в общем-то все равно. А задача вроде как параллелится только в путь.

PS: Ты на C-blocks смотрел?

Оно не все так радужно, то есть 4 потока не дают учетверения. Где-то двухсполовинивание, причем я именно о С-шном коде, который в память не уперт.

Ну а дальше правило Амдала тоже серьезно начинает мешать.

Правильно, но если взять SSEчто-то там версию, и упремся в память, то там уже все эти законы перестанут действовать, то есть дальше оптимизировать бессмысленно. Осталось только в память упереться, впрочем ты как раз и изучаешь, как это сделать.

Мне-то интересно, насколько реален такой сценарий в ближайшем будущем.