Скалярное произведение - быстрее, ещё быстрее!
Попытался соптимизировать под SSE4/4.1/4.2 функцию вычисления скалярного произведения двух векторов 32-битных чисел с плавающей точкой. Длина векторов фиксирована - 28 чисел (=28*4=112 байт). %rsi указывает на первый вектор (загружается в %xmm1-7), %rdi - на второй, результат в %xmm0.
Кое-какой выигрыш получил, но хочется больше. Что тут можно ещё оптимизировать? Что-то по-другому сделать? Попробовал также на AVX, но оказалось сильно медленнее.
Давай саму исходную задачу, из решения которой ты принёс кусок.
Спасибо, капитан. См. выше. Я векторизовал, но мне надо ускорить.
И да, использовать URL shorteners - плохой тон. Здесь не твиттер.
С чего ты взял, что результат компиляции оставляет место для дальнейшей оптимизации? У тебя тривиальный код, любой современный компилятор и так производит оптимальный код для него.
И да, использовать URL shorteners - плохой тон. Здесь не твиттер.
godbolt.org в первый раз видишь штоле?
Это годболт, он по другому не умеет. И даже если бы умел, то URL обычно получается слишком длинным чтобы им было удобно пользоваться.
любой современный компилятор и так производит оптимальный код для него.
На самом деле тот же gcc производит субоптимальный код:
$ gcc -O3 -msse -msse2 -msse3 -msse4 -msse4.1 -msse4.2 -c main.o main.c
$ objdump -d main.o
Как легко можно заметить, gcc даже не попытался размотать (unroll) цикл.
1. Зачем ты смотришь на результат компиляции кода с UB? Компилятор может сократить все до retq и будет прав. 2. Как ты определяешь «оптимальность» кода, на глаз?
Шланг вон размотал, но без бенчмарков это еще ничего не значит. Gcc умеет анроллить уже лет 20, если он этого не сделал, значит у него были на то основания.
2. Как ты определяешь «оптимальность» кода, на глаз?
Я гонял тесты в размотанном и неразмотанном варианте.
Как легко можно заметить, gcc даже не попытался размотать (unroll) цикл.
Сайт полезный, хотя я сомневаюсь, что автогенерированный код окажется быстрее затюненного вручную. Впрочем, надо проверить (и проверю).
С -funroll-loops разворачивает, но код далёк от оптимального, это даже на глаз видно.
Pipelining? Не, не слышали.
1) Почему в цикле не ++i (или это тоже автоматом оптимизируется уже)?
2) Почему бы не запихнуть оба вектора в один вектор парами:
c = [a0, b0, a1, b1, . a27, b27]
а потом вычислять в цикле примерно такое?
Но кто его знает сколько займёт времени на копирование в новый массив.
1) Почему в цикле не ++i (или это тоже автоматом оптимизируется уже)?
Потому, что i это POD, а не stl-итератор.
Я не пишу на C, я пишу на ассемблере. Код на C был для непонятливого анонимуса.
А вектора - они отдельные, не вперехлёст, и даже трансформация в такой вид ничего не даст инструкциям SIMD.
Почему бы тебе не выровнять данные как надо и не использовать movaps?
Оно хорошо, если ты контролируешь выделение памяти под данные. Если нет, придётся копировать, и время сразу просядет.
Можно попробовать срезать невыровненный кусок сначала с помощью movups, а потом делать movaps.
Дальше остается оптимизация обрашения к памяти (заполнение кеша).
Память у нас медленная. Промах кеша получается дорогой.
Замерь время чтения данных из массивов в регистры. Быстрее этого уже никак.
Что используется профилирования? Без нее дальнейшая оптимизация невозможна.
Дальше остается выполнять вычисления этой функции для разных массивов на разных ядрах.
Ага. Проблема в том, что она выполняет действия с регистрами, то есть надо загружать предварительно из памяти отдельной инструкцией. Инструкций получается больше, выполняется медленнее. Проверял уже.
Впрочем, буду рад, если кто-нибудь сможет меня опровергнуть и написать с dpps более быстрый код.
For some history: yes, movaps versus movups used to make a difference in performance. But since Intel's Nehalem chips the difference in negligable. As a compiler targeting newer hardware, if we get to choose between movups and movaps we are generally going to choose movups as there is no risk of your program crashing.
Время чтения - половина всего времени.
Про IEEE-compliant и не-compliant арифметику Великий Оптимизатор даже не слышал.
Теперь сравни свои ссылки с результатом для gcc из транка (который не боится использовать movups и ему не обязательно выравнивать массивы) + -Ofast: https://godbolt.org/g/w2pSRq
И зачем там нужен был __restrict, если там только читается, а не пишется по указателям?
А чей код быстрее — gcc или clang-а? https://godbolt.org/g/k5STnC
Опять ламерки в тред заехали.
Попробовал также на AVX, но оказалось сильно медленнее.
Не соответствует реальности.
Не пытаться писать парашу?
У тебя оно там не одно - вот и оптимизируй не одно.
Ага. Проблема в том, что она выполняет действия с регистрами, то есть надо загружать предварительно из памяти отдельной инструкцией.
И что же из этого следует?
Инструкций получается больше, выполняется медленнее.
Никак второе из первого не следует, в твоём случае, да и в большинстве случаев.
Впрочем, буду рад, если кто-нибудь сможет меня опровергнуть и написать с dpps более быстрый код.
Если ты хочешь, что-бы кто-то тебе что-то написал, то ты должен предоставить экосистему - тесты и бенчмарки, желательно всё в рамках конечного юзкейса.
Дальше остается оптимизация обрашения к памяти (заполнение кеша).
Память у нас медленная. Промах кеша получается дорогой.
Замерь время чтения данных из массивов в регистры. Быстрее этого уже никак.
Что используется профилирования? Без нее дальнейшая оптимизация невозможна.
Ахинея. Что ты собрался «профилировать»?
Дальше остается выполнять вычисления этой функции для разных массивов на разных ядрах.
Я даже не знаю что на эту потужную ретрансляцию мифов и легенд из-за парты отвечать.