x86 max / min ASM инструкции?
есть ли какие-либо инструкции asm, которые могут ускорить вычисление min/max вектора двойников/целых чисел на архитектуре Core i7?
обновление:
Я не ожидал таких богатых ответов, спасибо. Поэтому я вижу, что max / min можно сделать без ветвления. У меня есть под-вопрос:
есть ли эффективный способ получить индекс самого большого двойника в массиве?
6 ответов
SSE4 есть PMAXSD
или PMAXUD
для 32-битных целых чисел со знаком / без знака, что может быть полезно.
С SSE2 есть MAXPD
и MAXSD
которые сравнивают между и через пары двойников, поэтому вы следуете за n / 2-1 MAXPDs с одним MAXSD, чтобы получить максимум вектора n, с обычным переплетением нагрузок и операций.
существуют минимальные эквиваленты вышеизложенного.
для двойного случая вы, вероятно, не будете делать лучше в ассемблере, чем наполовину приличный C++ компилятор в режиме SSE:
peregrino:$ g++ -O3 src/min_max.cpp -o bin/min_max
peregrino:$ g++ -O3 -msse4 -mfpmath=sse src/min_max.cpp -o bin/min_max_sse
peregrino:$ time bin/min_max
0,40
real 0m0.874s
user 0m0.796s
sys 0m0.004s
peregrino:$ time bin/min_max_sse
0,40
real 0m0.457s
user 0m0.404s
sys 0m0.000s
где min_max вычисляет min и max массива из 500 удваивает 100 000 раз, используя наивный цикл:
bool min_max ( double array[], size_t len, double& min, double& max )
{
double min_value = array [ 0 ];
double max_value = array [ 0 ];
for ( size_t index = 1; index < len; ++index ) {
if ( array [ index ] < min_value ) min_value = array [ index ];
if ( array [ index ] > max_value ) max_value = array [ index ];
}
min = min_value;
max = max_value;
}
в ответ на часть вторую традиционная оптимизация для удаления ветвления из операции max заключается в сравнении значений, получении флага как одного бита ( дающего 0 или 1 ), вычитании одного ( дающего 0 или 0xffff_ffff) и " и " его с xor двух возможных результатов, поэтому вы получаете эквивалент ( a > best ? ( current_index ^ best_index ) : 0 ) ^ best_index )
. Я сомневаюсь существует простой способ SSE сделать это, просто потому, что SSE имеет тенденцию работать с упакованными значениями, А не с помеченными значениями; есть некоторые операции горизонтального индекса, поэтому вы можете попробовать найти max, а затем вычесть это из всех элементов в исходном векторе, затем собрать бит знака, и нулевой знак будет соответствовать индексу max, но это, вероятно, не будет улучшением, если вы не используете шорты или байты.
MAXPS и MINPS от SSE оба работают на упакованных числах с плавающей запятой одиночн-точности. PMAXSW, PMINSW, PMAXUB и PMINUB работают с упакованными 8-битными словами, подписанными или неподписанными. Обратите внимание, что они сравнивают два входных регистра SSE или местоположения адресов по элементам и хранят результат в регистре SSE или местоположении памяти.
версии SSE2 MAXPS и MINPS должны работать на поплавках двойной точности.
какой компилятор и оптимизации флаги вы используете? gcc 4.0 и лучше должны автоматически векторизовать операции, если ваша цель поддерживает их, более ранним версиям может потребоваться определенный флаг.
Если вы используете Intel IPP библиотека вы можете использовать вектор статистические функции для вычисления вектора min / max (среди прочего)
в ответ на ваш второй вопрос: на большинстве платформ, есть библиотеки, которые уже содержат оптимизированные реализации этой работы (и большинство других простых векторных операций). использовать их.
- на OS X, есть
vDSP_maxviD( )
иcblas_idamax( )
в ускорение.рамки - компиляторы Intel включают библиотеки IPP и MKL, которые имеют высокопроизводительные реализации, включая
cblas_idamax( )
- большинство систем Linux будет
cblas_idamax( )
в библиотеке BLAS, которая может быть или не быть хорошо настроена в зависимости от ее происхождения; пользователи, которые заботятся о производительности, как правило, имеют хорошую реализацию (или могут быть уговорены установить ее) - если все остальное не удается, вы можете использовать ATLAS (автоматически настроенное программное обеспечение линейной алгебры), чтобы получить достойную реализацию производительности на целевой платформе
Update: я только что понял, что вы сказали "массив", а не "вектор" в части 2. Я все равно оставлю это здесь на случай, если это пригодится.
re: часть вторая: найдите индекс элемента max/min в векторе SSE:
-
сделайте горизонтальный максимум. Для вектора 128b 2
double
элементы, это только одинshufpd
+maxpd
чтобы оставить результат трансляции для обоих элементов.для других случаев, он, конечно, будет принимать больше мер. Видеть самый быстрый способ сделать горизонтальную сумму вектора поплавка на x86 для идей, заменив
addps
Сmaxps
илиminps
. (Но обратите внимание, что 16-разрядное целое число является специальным, потому что вы можете использовать SSE4phminposuw
. Для max вычесть из 255) -
сделайте упакованное сравнение между векторным исходным вектором и вектором, где каждый элемент является максимальным.
(
pcmpeqq
целочисленные битовые шаблоны или обычныеcmpeqpd
оба будут работать наdouble
случай.) -
int _mm_movemask_pd (__m128d a)
(movmskpd
) чтобы сравнить результат как целое число растровых. - bit-scan (
bsf
) это для (первого) матча:index = _bit_scan_forward(cmpmask)
. cmpmask = 0 невозможно, если вы использовали целочисленные сравнения (потому что по крайней мере один элемент будет соответствовать, даже если они NaN).
это должно компилироваться только до 6 инструкций (включая movapd
). Да, только что проверил компилятор Godbolt исследователь!--32--> и это происходит с SSE.
#include <immintrin.h>
#include <x86intrin.h>
int maxpos(__m128d v) {
__m128d swapped = _mm_shuffle_pd(v,v, 1);
__m128d maxbcast = _mm_max_pd(swapped, v);
__m128d cmp = _mm_cmpeq_pd(maxbcast, v);
int cmpmask = _mm_movemask_pd(cmp);
return _bit_scan_forward(cmpmask);
}
отметим, что _mm_max_pd
не является коммутативным с входами NaN. Если NaN возможно, и вы не заботитесь о производительности на Intel Nehalem, вы можете рассмотреть возможность использования _mm_cmpeq_epi64
для сравнения битовых шаблонов. Bypass-delay от float до vec-int является проблемой на Нехалеме.
Нэн != NaN в IEEE с плавающей запятой, поэтому _mm_cmpeq_pd
маска результата может быть все-ноль в случае all-NaN.
другой то, что вы можете сделать в 2-элементном случае, чтобы всегда получать 0 или 1,-это заменить бит-сканирование на cmpmask >> 1
. (bsf
странно с input = all-zero).
в ответ на ваш второй вопрос, возможно, вам стоит подумать о том, как вы собираете и храните эти данные.
вы можете хранить данные в B-дереве, которое постоянно сортирует данные, требуя только логарифмических операций сравнения.
тогда вы всегда знаете, где максимум.