Эффективно вычисляйте степени X в SSE/AVX

Я ищу наиболее эффективный способ вычисления всех первых целых чисел с плавающей запятой X внутри регистра SSE-128/AVX-256/AVX-512 (128, 256 и 512 бит), например. для float AVX1-256 я хочу получить в регистре X^1, X^2, X^3, X^4, X^5, X^6, X^7, X^8 данные X на входе. Мне нужен код как для float-32, так и для double-64.

Я реализовал этот код для случая AVX-256/float-32:

Попробуйте онлайн!

__m256 _mm256_powers_ps(float x) {
    float const x2 = x * x, x4 = x2 * x2;
    return _mm256_mul_ps(
        _mm256_mul_ps(
            _mm256_setr_ps( x, x2,  x, x2,  x, x2,  x, x2),
            _mm256_setr_ps( 1,  1, x2, x2,  1,  1, x2, x2)
        ),
            _mm256_setr_ps( 1,  1,  1,  1, x4, x4, x4, x4)
    );
}

Я разработал его в основном, чтобы выглядеть просто. Но я думаю, что с точки зрения производительности это можно сделать быстрее, может быть, на одно или два умножения меньше. Кто-нибудь может предложить более быструю версию? Может быть, есть даже какая-то единая инструкция AVX для вычисления этих мощностей?

Меня интересуют версии с плавающей запятой 32 и двойной 64 для всех 128-битных (4 числа с плавающей запятой или 2 двойных), 256-битных (8 чисел с плавающей запятой или 4 двойных) и 512-битных SIMD-регистров (16 чисел с плавающей запятой или 8 двойных значений). ).

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

Попробуйте онлайн!

__m256 _mm256_powers_ps(float x) {
    float const x2 = x * x;
    __m256 const r = _mm256_mul_ps(
        _mm256_setr_ps( x, x2,  x, x2,  x, x2,  x, x2),
        _mm256_setr_ps( 1,  1, x2, x2,  1,  1, x2, x2)
    );
    return _mm256_mul_ps(r,
        _mm256_setr_m128(_mm_set1_ps(1),
            _mm_permute_ps(_mm256_castps256_ps128(r), 0b11'11'11'11))
    );
}

Я также думал о том, что простое решение без SIMD может быть также быстрым из-за хорошей параллельной конвейерной обработки многих независимых умножений:

Попробуйте онлайн!

__m256 _mm256_powers_ps(float x) {
    auto const x2 = x * x;
    auto const x3 = x2 * x;
    auto const x4 = x2 * x2;
    return _mm256_setr_ps(
        x, x2, x3, x4, x4 * x, x4 * x2, x4 * x3, x4 * x4);
}

Примечание: эти степени X необходимы для этапа вычисления полинома с плавающей запятой или двойной точности, см. мой другой вопрос относительно вычисления полиномов на SIMD. Этот многочлен может иметь разную степень, иногда 3, иногда 6, иногда 9, иногда 15, даже 25 или 32 или 43 степени. Эти числа произвольны, на самом деле может быть использован целый диапазон полиномов от 1 до 48 степеней. Коэффициенты многочленов заранее заданы как константы. Но значение X, для которого его надо вычислить, заранее неизвестно. Конечно, я буду использовать модуль FMA для вычисления самого значения полигонов, но для вычисления полигонов с использованием SIMD необходимы предварительно вычисленные мощности X.

Чаще всего будет использоваться целевой ЦП: Intel Xeon Gold 6230 с AVX-512, поэтому мне нужно оптимизировать код для него.


person Arty    schedule 13.06.2021    source источник
comment
Эта конструкция ошибочна. SSE/AVX работает лучше всего, когда у вас есть несколько данных для работы, с сильным акцентом на несколько. Но у вас есть только один элемент данных, X. Не ожидайте большого выигрыша от использования SSE/AVX для вашей спецификации.   -  person j6t    schedule 13.06.2021
comment
Поскольку вы пытаетесь получить ускорение для одного единственного ввода x, с каким окружающим кодом это должно перекрываться? Является ли он более чувствительным к задержке этой операции, или к стоимости пропускной способности внешнего интерфейса, или к нагрузке внутреннего порта на любом заданном порту? Как сказал @j6t, если у вас есть более одного значения x для этого, вычисляйте векторы с одной и той же операцией для всего вектора. При необходимости транспонируйте весь набор векторов 8x8 в конце, если вам нужно сохранить x1, x2, x3, ... в память в таком порядке, чтобы не тратить свои тасовки впустую.   -  person Peter Cordes    schedule 13.06.2021
comment
@ j6t Почему SIMD? Поскольку эти мощности необходимы как часть потока SIMD, существует множество векторизованных операций, но между ними есть этап вычисления мощностей. Поэтому я подумал, что это можно как-то сделать и в регистрах SSE/AVX. Более того, если вы посмотрите на мой код выше, он выполняет 4 умножения (3 умножения во 2-й версии) вместо 7 умножений, необходимых для версии без SIMD, поэтому это означает, что SIMD очень помогает здесь!   -  person Arty    schedule 13.06.2021
comment
(Кроме того, проверьте asm для _mm256_setr_ps — он может плохо работать и не понимать, что это просто 64-битная трансляция x, x2, которую он мог бы сделать с vmovlhdup xmm0, xmm1 / vmulss xmm0, xmm0 / vbroadcastpD ymm0, xmm0)   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Мне нужно оптимизировать задержку. У меня есть только один X. Этот вычислительный этап X Powers появляется в середине других SIMD-операций.   -  person Arty    schedule 13.06.2021
comment
Это математика FP, поэтому, конечно, она будет выполняться в SIMD-регистрах так или иначе на x86-64; скалярный FP использует регистры XMM. Но если это часть последовательности SIMD-операций, в какой форме фактически доступны ваши входные данные и в какой именно форме вам нужен результат? (И можно ли это изменить?)   -  person Peter Cordes    schedule 13.06.2021
comment
Значит, это часть одной длинной цепочки зависимостей, включающей только один вектор данных? Не является частью того, что вы делаете с несколькими векторами данных, что позволило бы неупорядоченному выполнению перекрывать независимую работу? В большинстве вариантов использования SIMD достаточно параллелизма данных, чтобы можно было легко создать параллелизм на уровне инструкций, поэтому задержка данных редко является единственным, что имеет значение с SIMD.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes На самом деле у меня есть X на входе как тип double. При необходимости его также можно передать как младшее слово 128-го или 256-го регистра. Результат нужен как 256-регистр.   -  person Arty    schedule 13.06.2021
comment
@PeterCordes Да, проделано много полновекторной работы, затем из результатов этой работы извлекается один X, степени вычисляются в 256-регистре, и снова много работы SIMD. Это не часть огромных данных, моя полная функция получает только одну точку данных.   -  person Arty    schedule 13.06.2021
comment
Хорошо, это, наконец, имеет какой-то смысл. Я полагаю, что более поздней работы, независимой от x, не существует? В любом случае, вы могли бы так же легко иметь x как младший элемент __m256 или __m128. Или, если это еще не нижний элемент, вы можете vpermps транслировать его вместо простого перетасовывания в самый низ. Это упрощает оптимизацию перетасовки вместо использования скаляра _mm_set.   -  person Peter Cordes    schedule 13.06.2021
comment
Обратите внимание, что существует встроенная функция _mm_mul_ss для [v]mulss, позволяющая просто умножить младший скалярный элемент и оставить остальные без изменений; это похоже на прецедент для него.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Не выполняя дополнительных измерений скорости, не могли бы вы взглянуть на два моих фрагмента кода выше и сказать, быстрее ли секунда? Второй выполняет на одно умножение меньше, но выполняет другие дополнительные действия по перестановке. В то время как первый, имеющий еще одно умножение, по-прежнему имеет два параллельных умножения, поэтому он должен быть хорошо конвейеризирован. Так что может быть, что первый может быть даже быстрее из-за хорошей конвейерной обработки и простоты.   -  person Arty    schedule 13.06.2021
comment
Вы говорите о дополнительных перестановках, но у первого есть 3 _mm256_setr_ps, что, если сделать это наивно, может потребовать много перетасовок для каждого! Вы даже не пытаетесь сконструировать одно из другого, и я предполагаю, что только у clang есть много шансов заметить сходство между ними и создать одно из другого путем перетасовки или смешивания вместо перезапуска со скалярных входных данных. Ты про асм? Если да, то с какими компиляторами вы заботитесь об эффективной компиляции? (На самом деле в вашей ссылке Godbolt даже gcc не так плох, как я думал, но, конечно, не так хорош.)   -  person Peter Cordes    schedule 13.06.2021
comment
Пробовали ли вы использовать IACA или LLVM-MCA для простого анализа конвейера (включая узкие места задержки)? Обычно они настроены только для обработки циклов, поэтому вам может потребоваться создать вызывающую программу, которая преобразует вектор обратно в скалярное число с плавающей запятой внутри цикла, чтобы сделать этот код зависимым от цикла. Затем вы можете попробовать что-то самостоятельно и получить автоматическую обратную связь. (И это дало бы вам что-то для микробенчмаркинга на реальном оборудовании.) Также обратите внимание, что вам не нужно включать main в Godbolt; это просто беспорядок.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Прямо сейчас я нацелен на компилятор CLang, он будет частью кода C++. Но в будущем может понадобиться и MSVC/GCC.   -  person Arty    schedule 13.06.2021
comment
@PeterCordes Никогда не слышал о IACA or LLVM-MCA и не знаю, как их использовать. Но было бы здорово научиться!   -  person Arty    schedule 13.06.2021
comment
Если вы заботитесь о том, чтобы это не было отстойно с MSVC, тогда вам лучше избегать этого _mm_setr_ps. Посмотрите на все эти наивные vinsertps на godbolt.org/z/bj5rK9jfP. Re: IACA и LLVM-MCA: см. (Как) я могу предсказать время выполнения фрагмента кода с помощью анализатора машинного кода LLVM? и Что такое IACA и как его использовать?. и погуглите их. Godbolt может добавить представление анализа llvm-mca к выходным данным компилятора.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Только что обновил сообщение о моем вопросе, добавил третий фрагмент кода с версией умножения без SIMD, что может быть еще быстрее из-за хорошей конвейерной обработки независимых умножений.   -  person Arty    schedule 13.06.2021
comment
@MarekR Только что добавил третий фрагмент к моему вопросу с простым общим кодом умножения C ++. Есть инструкции «Попробуйте онлайн-link there from which you can see that CLang created quite simple and stragihtforward code of single-double multiplications using vmulss». Таким образом, он не выполнял никаких комбинированных инструкций SSE/AVX.   -  person Arty    schedule 13.06.2021
comment
@Arty: да, оставив все это компилятору, у него хорошая ILP, но процессоры Intel до Ice Lake имели пропускную способность только 1/такт, поэтому все эти vinsertps инструкции для реализации _mm256_setr_ps (godbolt.org/z/fxxvsnncx) не могут работать параллельно друг с другом из-за конфликтов ресурсов (ограниченная внутренняя пропускная способность), поэтому минимальная задержка не так хорошо, как можно было бы надеяться. Тем не менее, он избегает объединения в цепочку любых умножений, и при задержке 4c они намного медленнее, чем перемешивание 1c. Стоит учесть.   -  person Peter Cordes    schedule 13.06.2021
comment
@Arty: Эээ, подождите минутку, по своей сути существует зависимость между инструкциями умножения в таких вещах, как x4 = x2*x2 необходимость ждать, пока x2 будет готов. Таким образом, перекрытие задержек умножения и перемешивания, вероятно, лучше, если делать это осторожно.   -  person Peter Cordes    schedule 13.06.2021
comment
Можете ли вы сделать так, чтобы ваш предыдущий код уже транслировал x на __m128 без использования каких-либо дополнительных инструкций по перемешиванию? например если это сумма массива, выполните hsum с перетасовкой, которая оставляет результат везде, т.е. высокая ‹-> низкая вместо высокой-> низкая перетасовка. Или это естественно внизу вектора без лишних перетасовок? (Или это скажем 2-й элемент, а остальные держат фигню?)   -  person Peter Cordes    schedule 13.06.2021
comment
(Наряду с mulss это сделало бы unpcklps более мощным для создания вектора, такого как x, x2, x,x2. shufps требует, чтобы оба элемента каждой 64-битной половины исходили из одного и того же источника, поэтому его трудно использовать с x и x2 в нижней части разных векторов. vpermps имеет только 1 вход. и unpcklpd / movlhps не очень помогают.)   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Вы можете подумать, что X находится внизу регистра YMM, остальное равно 0 (на самом деле может быть мусор, не проверял).   -  person Arty    schedule 13.06.2021
comment
Что вы хотите сделать с этой функцией. Что касается использования, возможно, можно значительно ускорить код, используя математические приемы.   -  person Jérôme Richard    schedule 13.06.2021
comment
Пара идей дизайна: godbolt.org/z/zqGv51rn3, оба с одинаковой задержкой критического пути (3 умножения, 4x 1c в случайном порядке, 1x 3c в случайном порядке при пересечении дорожек). Две перетасовки могут выполняться параллельно на Zen/Ice Lake. Одна версия имеет дополнительное умножение (начиная с того же цикла, что и другая). Я надеялся получить больше пользы от mulss. Я мог бы написать это как ответ когда-нибудь.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Спасибо за примерный код на godbolt. Забыл упомянуть. Для моей задачи мне действительно нужны решения для всех 128, 256, 512 регистров (с максимальной степенью 4, 8, 16). Не из-за разных целевых процессоров, а из-за разных случаев ввода - для некоторых входов потребуется 4 мощности, для некоторых 8 мощностей, для некоторых 16 мощностей, в зависимости от некоторого порога. Также мне нужны решения как с плавающей запятой-32, так и с двойным-64. Это просто заметка для вас, если это как-то повлияет на ваше решение.   -  person Arty    schedule 13.06.2021
comment
Подождите, а у вас есть AVX-512 даже для узких версий?? vpermt2ps может спасти перетасовку, а маскирование слиянием для умножения и перетасовки может быть очень полезным. Можно ли с пользой повторно использовать константы векторов и масок, или между этими шагами нужно проделать много работы? (Я предполагаю, что может быть стоит перезагрузить константу, если она экономит задержку критического пути, если она не кэширует промахов больше, чем сохраняет. Настройка констант, конечно, всегда вне критического пути.) Или вам это нужно для актуальное наследие-SSE и AVX1? Для устаревшего SSE дополнительные копии регистров movaps могут иметь значение.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Я не могу гарантировать какой-либо целевой процессор, поэтому мне приходится писать код SSE2/AVX1/AVX-512 отдельно. Но даже для машины AVX-512 при некоторых пороговых значениях может случиться так, что версия 256 будет быстрее, чем версия 512, из-за математики этого предварительного вычисления. Конечно, если это поможет на машине AVX-512, можно выполнить 256 вычислений, используя половину регистра 512, если половина avx-512 по какой-то причине будет быстрее, чем avx1-256. Но мне также нужно ориентироваться на ЦП, у которых есть только SSE2, или ЦП, которые имеют только SSE2+AVX1, или ЦП, у которых есть все SSE2+AVX1/2+AVX-512.   -  person Arty    schedule 13.06.2021
comment
@PeterCordes Например, для ввода некоторого мнимого значения K <= 6 быстрее использовать 128-битные вычисления даже на машине AVX-512, поэтому мне нужно использовать 25% всего регистра AVX-512, чтобы увеличить скорость. Для другого порога 6 < K <= 13 мне нужно использовать 256 бит даже на машине AVX-512. А для 13 < K могу использовать полный AVX-512. K здесь просто некий воображаемый параметр, контролирующий порог принятия решения о выборе 128/256/512, а не количество поплавков. Таким образом, 512 не всегда является самым быстрым способом вычисления конечной задачи. Не задача сил, а полная задача моей функции.   -  person Arty    schedule 13.06.2021
comment
Любите ли вы какую-либо из этих версий больше, чем другие? Это слишком много разных версий, чтобы вручную оптимизировать их все по отдельности, если только это не является действительно важной частью вашей программы, и сохранение цикла задержки имеет большое значение. (Я имею в виду, что если бы вы хотели заплатить мне за оптимизацию каждой версии этого для каждой комбинации уровня функций SIMD и ширины вектора/элемента, я бы, конечно, был готов это сделать. А также настроить разные версии для Zen 1 и , Ice Lake против Skylake, если хотите. Но для ТАКОГО ответа это явно слишком много повторений.)   -  person Peter Cordes    schedule 13.06.2021
comment
AVX-512 предоставляет несколько принципиально новых инструментов, которые отлично подходят для любой ширины вектора, поэтому я ожидаю, что они будут значительно отличаться. Я еще не видел применения для insertps, и до сих пор мои идеи для AVX1 в основном использовали вещи, которые будут работать в SSE2 (нет необходимости расширяться до 256 на ранней стадии, когда это было бы избыточно и нуждалось бы в связке 1.0), и строит результат __m256 из __m128. Однако я не пытался минимизировать в нем movaps инструкций. Но я ожидаю, что double будет существенно отличаться от float, вероятно, намного проще.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Я думаю, что большинство моих процессоров будут иметь AVX-512, поэтому мы можем придерживаться этой версии. Но как я уже говорил даже в рамках avx-512 мне 100% нужны все три 128/256/512. Это означает, что для некоторых входных чисел мне нужно 4 степени с плавающей запятой, для некоторых 8 степеней с плавающей запятой для некоторых 16 степеней с плавающей запятой. Таким образом, даже если используется регистр 512, то для четырех степеней с плавающей запятой нам нужно использовать только первые 128 бит. Реально-реальной наиболее распространенной задачей ввода будет случай 256 бит, а это означает, что, как вы уже начали, вы можете использовать регистр AVX1-256 с 8 числами с плавающей запятой, и я каким-то образом сам обобщу его на другие случаи. На самом деле 16 поплавков редкость.   -  person Arty    schedule 13.06.2021
comment
Итак, я думаю, вы в основном собираетесь запускать это на современных серверах? Так что вы, вероятно, заботитесь о настройке Skylake-X и, возможно, будущего Ice Lake. Вы действительно должны указать некоторые из этих деталей в вопросе и пометить его как avx-512.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Иногда мне нужно вычислить всего 4 степени (с плавающей или двойной), иногда 8 степеней (с плавающей или двойной), иногда 16 степеней (с плавающей или двойной). И это не зависит от процессора, а зависит от входных значений моей функции.   -  person Arty    schedule 13.06.2021
comment
Ага, вы уже четко изложили требования корректности. Я думал о настройке, как я уже сказал. У Ice Lake есть еще одно устройство тасования на порту 1, которое может запускать несколько часто используемых операций тасования; SKX нет.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Мой код будет запускаться в основном на Intel Xeon Gold 6230 с AVX-512.   -  person Arty    schedule 13.06.2021
comment
@PeterCordes На самом деле, чтобы вы знали - эти степени X необходимы для этапа вычисления полинома с плавающей запятой или двойной точности. Этот полином может иметь разные степени, иногда 3, иногда 6, иногда 9, иногда 15, даже 25 или 32 степени. Эти числа произвольны, на самом деле может быть использован целый диапазон полиномов от 1 до 32 степеней. Коэффициенты многочленов заранее заданы как константы. Но значение X, для которого его надо вычислить, заранее неизвестно. Конечно, я буду использовать модуль FMA для вычисления самого полигона, но нужны и мощности.   -  person Arty    schedule 13.06.2021
comment
Когда вам нужен только 256-битный результат, будут ли окружающие инструкции включать какие-то 512-битные векторные инструкции? Если да, векторные ALU на порту 1 будут отключены, что сделает невозможным запуск двух умножений и перемешивания в одном и том же цикле. Это вероятно не будет иметь значения, и, поскольку вы говорите, что независимой работы нет, это просто код, ограниченный ILP.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes Когда мне нужен результат 256, все окружающие инструкции также будут 256-битными.   -  person Arty    schedule 13.06.2021
comment
Вы используете это для оценки многочленов?? Почему не FMA с коэффициентами как у вас идет, вроде нормально? См. github.com/vectorclass/version2/blob/ ( включая комментарий об укорочении цепочек зависимостей для полиномов более высокого порядка.) Или вам нужны все члены по отдельности, а не только сумма?   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes На самом деле, если я вычисляю мощности X^1, ..., X^8, то впоследствии мне нужно как-то разделить их на два регистра, первый регистр должен содержать 1, X^1, ..., X^7 (примечание 1 на первом месте), второй регистр должен содержать X^8, X^8, ... X^8 (значение X^8 передается в эфир). Так что если вы знаете, как конвертировать в такие два регистра, пожалуйста, скажите.   -  person Arty    schedule 13.06.2021
comment
Хм, почему бы не вычислить 1.0, x, x2, x3, ..., которое вам нужно, в первую очередь, вместо x1 .. x8? Вычисление set1(x^8) вместе с этой последовательностью, вероятно, так же просто. Но если вы усложнили себе задачу, используйте AVX512F valignd для перехода на 1.0 и используйте vpermps для трансляции верхнего элемента.   -  person Peter Cordes    schedule 13.06.2021
comment
@PeterCordes В вашем связанном исходном коде poly вычисляется по одному значению за раз, без использования SIMD (фактически с использованием SIMD, но с одним значением FMA, т. е. _mm256_fmadd_ss()). Но если мой полигон имеет степень вроде 48, то гораздо быстрее предварительно вычислить степени X^1... X^8, а затем выполнить FMA с 8 или 16 числами с плавающей запятой за раз. Ваш связанный источник оптимален для степеней ниже 6. Начиная со степени около 8, SSE2 уже помогает. И с 16 степени помогает AVX1, с 32 - AVX512. И у меня много 32-48-градусных полигонов.   -  person Arty    schedule 13.06.2021
comment
Он оптимизирован для параллельного вычисления одного и того же многочлена для 4 или 8 различных значений x, используя SIMD для того, в чем он хорош (вертикальные операции, выполнение одного и того же действия с несколькими значениями вместо горизонтальных). Если вы не можете этого сделать, может быть еще эффективнее включать коэффициенты в какой-то момент по ходу дела. Или какая-то гибридная стратегия, включающая несколько разных коэффициентов одного и того же полинома в одном векторе, но также, возможно, с использованием FMA. например может быть, запустить четные и нечетные степени или просто силы из 1..4, чтобы потом меньше перетасовывать в hsum.   -  person Peter Cordes    schedule 13.06.2021
comment
Да, если подумать об этом еще немного, вероятно, имеет смысл расширить максимум до 1/4 степени полинома и зациклить с несколькими аккумуляторами по массиву коэффициентов. например два вектора x1 .. x4 (или другой, возможно, x5..x8?), зацикливая FMA по массиву коэффициентов (начиная с конца или сохраняя его, начиная с самого высокого коэффициента, и в этом случае вы хотите x4..x1 ). Вы получаете от x1..x4 до x9..x12, умножая на set1 (x8), поэтому вам нужно создать это из вашего вектора set1 (x4), но параллельно с FMA из массива.   -  person Peter Cordes    schedule 14.06.2021
comment
Я думаю, что в конце концов вы можете объединить два соседних аккумулятора только с FMA с set1 (x4), затем (если у вас было 4 аккумулятора) эти пары с set1 (x8). Это должно получить все суммированные коэффициенты, умноженные на достаточное количество x, чтобы получить правильное конечное значение. Тогда у вас есть сумма из 4 элементов, которая требует 2 перетасовки и 2 добавлений.   -  person Peter Cordes    schedule 14.06.2021
comment
Я думаю, что использование SIMD для ускорения оценки одного полинома является более полезным и широко применимым вопросом, чем то, что вы задали; вам, вероятно, следует изменить его, чтобы вместо этого спросить о более крупной проблеме, поскольку это тот случай, когда оптимизация работы в окружающем коде имеет смысл. Или, может быть, задайте отдельный вопрос, и этот вопрос может быть просто строительным блоком. (И обычно фокусируется только на необходимости расширения ограниченного количества. Расширение при запуске означает еще один шаг перетасовки/добавления уменьшения в конце, поэтому он стоит дорого.)   -  person Peter Cordes    schedule 14.06.2021
comment
@PeterCordes Задавая еще один вопрос о вычислении полигонов в SIMD, я боюсь получить отрицательный отзыв, например, что вычисление полигонов вообще не для SIMD, что SIMD в основном полезен для вертикальной векторизации и т. д. Но я провел собственное исследование, я уже реализовал вариант без SIMD и SIMD (SSE2, AVX1) и сравнил тайминги всего этого - и оказалось, что вариант SSE2 дает примерно 2x ускорение на больших полигонах вроде 30 градусов, а на более мелких полигонах вроде 8-10 градусов даже на них дает 1.4-1.5x ускорение. AVX1 дает 3.5x ускорение на больших полигонах (30 градусов) и около 1.5x на меньших 8,10   -  person Arty    schedule 14.06.2021
comment
@PeterCordes Только что создал еще один вопрос, как вы предложили, относительно вычисления полигонов на SIMD.   -  person Arty    schedule 14.06.2021
comment
@PeterCordes Вы заметили мой предыдущий комментарий? Также не могли бы вы сказать - если у меня AVX-512, значит ли это, что наборы SSE2 и AVX1 в этом ЦП реализованы с использованием набора вычислительных модулей AVX-512 в ЦП? Это означает, что SSE2/AVX1 внутри ЦП не имеют отдельных вычислительных модулей, они используют модули AVX-512? Означает ли это, что скорость одних и тех же математических операций для SSE2, AVX1, AVX-512 будет одинаковой на данном фиксированном процессоре? Например, если я вызову _mm_mul_ps() или _mm256_mul_ps() или _mm512_mul_ps(), то все они займут одинаковое количество циклов на одном процессоре?   -  person Arty    schedule 14.06.2021
comment
Посмотрите на uops.info и agner.org/optimize. vmulps xmm (и FMA) конкурируют за те же порты выполнения, что и vmulps ymm и zmm. (Хотя с 512-битным ZMM есть забавная штука, отключение порта 1, но есть отдельный 512-битный блок FMA на порту 5.)   -  person Peter Cordes    schedule 14.06.2021
comment
@PeterCordes Означает ли это, что если я использую только половину регистра 512 (верхняя половина равна нулю), то лучше использовать _mm256_mul_ps() для отлитой нижней части вместо использования _mm512_mul_ps()? То же самое для ФМА?   -  person Arty    schedule 14.06.2021
comment
Да, конечно. Он тратит меньше энергии (позволяя использовать более высокий турбо), а также избегает эффектов дросселирования тактовой частоты при использовании широких SIMD-инструкций. Используйте 512-битные векторы только тогда, когда вы действительно можете использовать их в полной мере в течение длительного времени. Инструкции SIMD снижают частоту процессора   -  person Peter Cordes    schedule 14.06.2021
comment
@PeterCordes Также всегда хотел задать кому-нибудь вопрос - знаете ли вы, может ли CLang обнаруживать независимые общие математические шаблоны с плавающей запятой и каким-то образом автоматически организовывать эти операции в векторы SSE2 / AVX1 / AVX512? Вам нужно указать -ffast-math для этого? Я заметил, что CLang иногда объединяет в регистры SSE2-128 две двойные операции. Но я никогда не замечал, что он объединяется в операции AVX1-256 (4 двойных), даже если установлен флаг -mavx. Есть ли способ предложить CLang объединиться в AVX1-256 и AVX-512 с помощью какого-то специального флага командной строки?   -  person Arty    schedule 14.06.2021
comment
Вопрос в том, стоит ли это делать. Используйте -march=native, а не только -mavx, чтобы сообщить clang, для какой машины он настраивается, а не только для того, какие расширения ISA доступны. Но обратите внимание, что перетасовки с пересечением дорожек имеют более высокую задержку (3c против 1c), поэтому реже стоит делать слишком много перетасовки вместе. Очевидно, что в цикле или что-то в этом роде он будет автоматически векторизовать массив с широкими векторами (вплоть до ширины предпочтительного вектора по умолчанию, которая часто равна 256 даже в системах с поддержкой AVX-512). -ffast-math иногда может помочь даже в тех случаях, когда оптимизация кажется вам законной без, особенно. для ГЦК.   -  person Peter Cordes    schedule 14.06.2021
comment
@PeterCordes Спасибо! Заметили ли вы мой другой вопрос относительно SIMD poly eval, вы хотели/предложили его спросить?   -  person Arty    schedule 14.06.2021
comment
Да, я это заметил. Пока не успел сделать больше, чем прочитать.   -  person Peter Cordes    schedule 14.06.2021
comment
@PeterCordes Можете ли вы также предложить, если какая-то встроенная функция C ++ имеет более одного результирующего регистра SIMD, то какой наиболее эффективный способ для компилятора вернуть эти регистры? Например, для 3 регистров возврата я должен иметь возвращаемое значение, такое как std::tuple<__m256, __m256, __m256>, или, может быть, лучше иметь один возврат __m256 плюс два указателя __m256 * ret в качестве параметров функции? Или даже ссылки типа __m256 & ret в качестве параметров? В случае, если это встроенная функция. Или решения для кортежа, указателя и эталона одинаковы для оптимизации на стороне компилятора? Также, если не встроенная функция, что лучше?   -  person Arty    schedule 14.06.2021
comment
По ссылке или указателю выходные аргументы обычно полностью оптимизируются после встроенной функции. Вероятно, так же поступил бы и std::tuple. Проверьте asm на Godbolt, конечно.   -  person Peter Cordes    schedule 14.06.2021
comment
@PeterCordes Еще один вопрос, связанный с poly eval - как наиболее эффективно вычислить сумму всех 4 чисел с плавающей запятой внутри 128 регистра? Сумма должна быть возвращена в C++ как обычное число с плавающей запятой. Это через выполнение _mm_hadd_ps() два раза, а затем _mm_cvtss_f32()? Или, выполнив _mm_store_ps() для некоторого выровненного по tmp массива a из 4 чисел с плавающей запятой, а затем вычислив a[0] + a[1] + a[2] + a[3]? Какой способ быстрее? Какая может быть разница во времени цикла для этих двух способов? Как насчет того же вопроса для регистра 256 (сумма 8 поплавков)?   -  person Arty    schedule 14.06.2021
comment
Если бы вы погуглили, то, вероятно, нашли бы самый быстрый способ суммировать вектор SSE по горизонтали (или другое сокращение), который охватывает то подробно.   -  person Peter Cordes    schedule 14.06.2021
comment
@PeterCordes Еще один вопрос, если вы не возражаете - если я использую _mm512_mul_ps() avx512, сколько независимых таких операций можно выполнять параллельно на современном процессоре, например Skylake? Во встроенном руководстве Intel я вижу задержку 4 и пропускную способность 0,5 для Skylake. Что это на самом деле означает? Означает ли это, что если у меня есть 4 смежные независимые инструкции _mm512_mul_ps(), то все 4 будут работать параллельно? Так что 1-я инструкция завершится на такте 4, 2-я на такте 4,5, 3-я на такте 5, 4-я на такте 5,5? Как насчет 8 инструкций? Какое оптимальное количество? 2 или 4 или 8 мул инструкции?   -  person Arty    schedule 14.06.2021
comment
Это означает 8 (независимых) mul/add/FMA в полете, чтобы насытить блоки FMA, начиная с двух в каждом цикле. задержка и пропускная способность во встроенных функциях Intel / Почему mulss взять всего 3 такта на Haswell, в отличие от таблиц инструкций Агнера? (Развертывание циклов FP с несколькими аккумуляторами). Конечно, с 512-битными uops есть только 2 порта выполнения для любой векторной инструкции ALU, поэтому перетасовка и смешивание (но не загрузка/сохранение) конкурируют за ограничение пропускной способности 2/такт.   -  person Peter Cordes    schedule 14.06.2021
comment
См. также Сколько циклов ЦП требуется для каждой инструкции сборки? для получения дополнительных сведений о параллелизме на уровне инструкций и пропускной способности по сравнению с задержкой.   -  person Peter Cordes    schedule 14.06.2021
comment
@PeterCordes Спасибо! Прежде чем я прочитаю все эти ссылки, не могли бы вы сказать, есть ли для этого онлайн-калькулятор? Например, я говорю калькулятору, что использую инструкцию mul или, может быть, fmadd и говорю, какой у меня процессор, например Skylake. Затем калькулятор подсказывает, сколько оптимальных независимых инструкций такого типа я должен разместить рядом друг с другом, чтобы насытить весь умножающий модуль? Мне нужно знать это и для старых процессоров, а также для AVX1-256 и SSE2-128, а также для float-32 и double-64.   -  person Arty    schedule 14.06.2021
comment
Анализ LLVM-MCA на Godbolt, как мы уже обсуждали, может анализировать петлю и находить узкие места в задержке и/или пропускной способности, а также показывать вам распределение uops -> портов, чтобы вы могли видеть, есть ли много запасных циклов на портах 0 и 1. (или порты 0 и 5 для 512-битных FMA). Инструкции смотрите на uops.info -› порты без необходимости писать код с их использованием.   -  person Peter Cordes    schedule 14.06.2021