Как стать автором
Обновить

Комментарии 15

Вообще — откуда такой упор на -О0? Хотя бы -Og использовали…
Хотелось максимально полного контроля: что написал, то и получил.
Для этого надо asm использовать. А то интринсики — это такое. Например, в произвольный момент времени (при использовании больше 6-8 SIMD-переменных) компилятор может напихать левых сохранений-восстановлений регистров через память.
Кстати, для последнего случая (с накоплением промежуточной суммы в SIMD-регистре) имеет смысл делать сравнение-накопление в двух регистрах по очереди, т.к. современные процессоры могут выполнять команды vpcmpeqw/vpand/vpaddw в количестве двух штук на такт (в отличии от vmovmskb, где только одну команду на такт), но с latency 1 такт. Соответственно, при исполнении через один регистр в теории теряется половина производительности. Правда на практике узким местом может быть уже чтение из памяти, но это надо на тесте проверять (и заодно и убедиться что компилятор не выкинул второй регистр, если тестовый код на интринсиках будет).
Кстати-2: можно заменить vpand с маской на vpsrlw на 15 разрядов. По производительности будет то же самое, но не нужно маску в отдельном регистре хранить. Пустячок, а приятно (и полезно в случае сложного кода, когда каждый регистр на счету).
когда каждый регистр на счету

Есть такие вещи как Register renaming, shadow register и "micro operations".
В AMD Zen сериях 1000 и 2000 инструкции AVX вроде как работают на 128 битных регистрах, т.ч. нужно делать два прохода.
Процессоры Intel снижают частоту при исполнении AVX.
Т.ч. всё сложно.
Есть такие вещи как Register renaming, shadow register

Вещи конечно есть, но их невозможно контролировать и они разные в разных сериях процессоров. А вот явно заявленные регистры есть всегда.
В AMD Zen сериях 1000 и 2000 инструкции AVX вроде как работают на 128 битных регистрах, т.ч. нужно делать два прохода.

Я имел мало опыта с АМД, но, судя по тестам Фога (некий известный в узких кругах Agner Fog) для Ryzen 7 1800X, немалое количество типовых операций делается с производительностью больше чем 1 регистр за такт, но с latency 1 такт, и для 256-разрядных регистров тоже. Т.е. 256-разрядные регистры имеет смысл использовать в любом случае, чтобы не терять производительность.
Процессоры Intel снижают частоту при исполнении AVX.
Т.ч. всё сложно.

Для этого и нужны тесты. Но по моему опыту, весьма немаленькому, на Интеле AVX2 даёт выигрыш всегда (хотя в ядрах с архитектурой Haswell этот выигрыш может быть совсем незначительным). Всё сложно — это с AVX512. Вот там действительно частота нехило снижается и другие важные тонкости есть.

Кстати, я что-то ступил в предыдущем предложении по модификации алгоритма с vpand с маской на vpsrlw. В итоге ни одна из этих операций вообще не нужна. Достаточно сделать vpcmpeqw, а затем vpsubw, т.к. по результатам сравнения мы получаем либо 0, либо -1 (0xFFFF).
А какая разница — vsubw или vandw? Всё равно если данных много переполнение будет…
Разница в одну инструкцию: первом случае vpcmpeqw -> vpand -> vpaddw, а во втором только vpcmpeqw -> vpsubw. По идее, это само по себе может 30% выигрыша дать на больших массивах.
А переполнение легко контролируется дополнительной вложенностью цикла. Т.е. делается цикл продолжительностью ARR_SIZE >> 20 (для случая 16 аккумуляторов в SIMD-регистре, по 16 разрядов каждый), внутри него цикл на 0x100000. И «хвост» продолжительностью ARR_SIZE & 0xFFFFF (на который также идёт переход, если ARR_SIZE < 0x100000). После каждого «малого» цикла значения аккумуляторов добавляются в регистр общего назначения нужной разрядности.
Разница в одну инструкцию: первом случае vpcmpeqw -> vpand -> vpaddw, а во втором только vpcmpeqw -> vpsubw. По идее, это само по себе может 30% выигрыша дать на больших массивах.
Не обратил внимания, что вы предлагаете vpand вставлять в цикл. Я думал там просто сложение… и в конце уже только, перед выдачей результата пользователю, вернуть минус сумму.
Не обратил внимания, что вы предлагаете vpand вставлять в цикл.

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

Можно и так, но один разряд аккумуляторов на знак уйдёт. Да и зачем лишние сложности в алгоритме, если можно просто поменять команду и сразу получать сумму с нужным знаком.
Из моей практики «что написал, то получил» — это примерно -O2. На -O3 начинается разворот циклов и прочие чудеса, которые, действительно, делают программу мало похожей на исходник, но вот -O0 сравнивать по скорости уж как-то совсем бессмысленно: бесконечные пересылки данных занимают куда больше времени, чем осмысленная деятельность.

Минимум, который имеет смысл сравнивать по скорости — это -Og, как я сказал: пересылки данных убиваются, по возможности, но программа остаётся линейной и инструкции не переставляются…
Что то мне подсказывает, что если просто включить -O2 или -O3 результат будет не сильно хуже. А если запустить профилирование то компилятор сгенерирует для выборки на N (где N явно большое) элементов даже более оптимальную реализацию чем можно написать руками.

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

Собственно я не исключаю, что могу быть не прав. Но как то слабо верится, что до сих пор всё так плохо особенно если использовать профилирование.
оптимизировать кеширования имеет смысл при хитрой стратегии обращения к памяти (не этот случай), а аллокации — при большом числе выделений/освобождений памяти (тоже не наш случай). А вот тело цикла — пожалуйста, статья как раз про это.

Один программист по имени Wojciech Muła публикует статьи по практическому применению SIMD: http://0x80.pl/articles/index.html


Мне нравится его подход со сравнением разных реализаций для одной конкретной задачи.

а можно ссылку на бенчмарки?
В начале статьи под спойлером текст
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории