#c #avx
Вопрос:
У меня есть массив с плавающей точкой,содержащий A,B,C, D 4 числа с плавающей точкой, и я хочу загрузить их в __m256
переменную, такую как AABBCCDD. Как лучше всего это сделать? Я знаю, что использование _mm256_set_ps()
-это всегда вариант, но он кажется медленным с инструкциями по 8 процессорам. Спасибо.
Комментарии:
1. Есть ли у вас AVX2, для
vpmovzdq ymm, mem
которого (2 uops на Intel) нужно настроитьvmovsldup
? Или просто AVX2vpermps
с постоянной вектора перетасовки, после 128-битной загрузки.2. @PeterCordes да AVX2 доступен. Я ориентируюсь на обычный настольный процессор
3. Хорошо, тогда я бы рекомендовал принять мой ответ. Это, по крайней мере, так же хорошо, как ответ Майка на современные основные процессоры с AVX2. (Или с помощью clang компилируется в тот же asm.)
Ответ №1:
Если бы ваши данные были результатом другого векторного вычисления (и в __m128), вам понадобился бы AVX2 vpermps
( _mm256_permutexvar_ps
) с управляющим вектором _mm256_set_epi32(3,3, 2,2, 1,1, 0,0)
.
vpermps ymm
составляет 1 uop на Intel, но 2 uop на Zen2 (с пропускной способностью 2 цикла). И 3 uops на Zen1 с пропускной способностью по одному на 4 такта. (https://uops.info/)
Если это был результат отдельных скалярных вычислений, вы можете перемешать их вместе с _mm_set_ps(d,d, c,c)
(1x vshufps), чтобы настроить для vinsertf128.
Но с данными в памяти, я думаю, что лучше всего использовать 128-битную широковещательную загрузку, а затем перетасовку в полосе. Для этого требуется только AVX1, а на современных процессорах это 1 загрузка 1 перетасовка uop на Zen2, Haswell и более поздних версиях. Он также эффективен на Zen1: единственной перетасовкой при пересечении полосы движения является 128-битная широковещательная загрузка.
Использование перетасовки в полосе движения имеет меньшую задержку, чем пересечение полосы движения как на Intel, так и на Zen2 (256-разрядные исполнительные устройства для перетасовки). Для этого по-прежнему требуется 32-байтовая константа вектора управления перетасовкой, но если вам нужно делать это часто, она, как правило / надеюсь, останется горячей в кэше.
__m256 duplicate4floats(void *p) {
__m256 v = _mm256_broadcast_ps((const __m128 *) p); // vbroadcastf128
v = _mm256_permutevar_ps(v, _mm256_set_epi32(3,3, 2,2, 1,1, 0,0)); // vpermilps
return v;
}
Современные процессоры обрабатывают широковещательные загрузки прямо в порту загрузки, не требуется перестановка uop. (Sandybridge действительно нуждается в uop для перетасовки портов 5 vbroadcastf128
, в отличие от более узких передач, но Haswell и более поздние версии являются чисто портами 2/3. Но SnB не поддерживает AVX2, поэтому перетасовка при пересечении полосы движения с детализацией менее 128 бит не была вариантом.)
Поэтому, даже если AVX2 доступен, я думаю, что инструкции AVX1 здесь более эффективны. На Zen1 vbroadcastf128
это 2 uops против 1 для 128-битного vmovups
, но vpermps
(пересечение полосы) составляет 3 uops против 2 для vpermilps
.
К сожалению, clang пессимизирует это в vmovups
нагрузку и a vpermps ymm
, но GCC компилирует его так, как написано. (Божья молния)
Если вы хотите избежать использования векторной константы управления перетасовкой vpmovzxdq ymm, [mem]
(2 uops на Intel), можно настроить элементы для vmovsldup
(1 uops в режиме перетасовки). Или транслировать-загружать, а vunpckl/hps
затем смешивать?
Я знаю, что использование _mm256_set_ps() всегда возможно, но это кажется медленным с 8 инструкциями процессора.
Тогда найдите лучший компилятор! (Или не забудьте включить оптимизацию.)
__m256 duplicate4floats_naive(const float *p) {
return _mm256_set_ps(p[3],p[3], p[2], p[2], p[1],p[1], p[0],p[0]);
}
компилируется с помощью gcc (https://godbolt.org/z/dMzh3fezE) в
duplicate4floats_naive(float const*):
vmovups xmm1, XMMWORD PTR [rdi]
vpermilps xmm0, xmm1, 80
vpermilps xmm1, xmm1, 250
vinsertf128 ymm0, ymm0, xmm1, 0x1
ret
Так что 3 тасовки, не очень хорошо. И его можно было бы использовать vshufps
вместо vpermilps
того, чтобы экономить размер кода и позволять ему работать на большем количестве портов на Ледяном озере. Но все равно значительно лучше, чем 8 инструкций.
оптимизатор перемешивания clang делает то же самое, что и с моими оптимизированными внутренними функциями, потому что именно таков clang. Это довольно приличная оптимизация, просто не совсем оптимальная.
duplicate4floats_naive(float const*):
vmovups xmm0, xmmword ptr [rdi]
vmovaps ymm1, ymmword ptr [rip .LCPI1_0] # ymm1 = [0,0,1,1,2,2,3,3]
vpermps ymm0, ymm1, ymm0
ret
Комментарии:
1. Если доступ к соседним элементам
p[-2], ..., p[5]
является сохраненным, можно также загрузить этот вектор вместо широковещательной передачи и выполнить перетасовку в полосе.2. Я действительно надеюсь, что у меня больше знаний о компиляторах и выборе компиляторов, для меня это как черный ящик, поэтому я бы предпочел не учитывать факторы компилятора на данном этапе
3. @Noob: Да, я бы рекомендовал написать оптимальные встроенные функции, как только вы потратите время на то, чтобы понять, как это будет выглядеть, если вы хотите, чтобы ваш код хорошо компилировался с несколькими компиляторами. (например, посмотрев на вывод clang
_mm256_setr_ps
и превратив его обратно в встроенные функции.) Но использование хорошего компилятора, такого как clang, означает, что вы можете получить хорошие результаты с гораздо меньшим количеством работы, и компилятор часто найдет лучшие способы сделать что-то, когда/если вы сами не придумаете лучший способ. Основной смысл этого ответа заключается в первом блоке кода, использующем_mm256_broadcast_ps
и_mm256_permutevar_ps
4. @чтз Я вижу, что загрузка, похоже, имеет меньшую задержку, чем трансляция. Или загрузите все,что содержит A,B,C, D, например, p[0]~p[7], если не произойдет нарушения доступа к памяти.
5. @чтз и Нуб: Но смотри также agner.org/optimize/blog/read.php?i=872amp;v=f#854 — 128-битная нагрузка с нулевым расширением, потребляемая 256-битной векторной операцией, также имеет дополнительную задержку. Так что, вероятно, на самом деле это 7 1 против 7 3 циклов. А 256-битная
vmovups ymm, [mem]
загрузка составляет 7 циклов. (Или, что еще хуже, при разделении строк кэша, поэтому на процессорах с дешевымиvbroadcastf128
процессорами лучше всего просто это сделать.)
Ответ №2:
_mm_load_ps -> _mm256_castps128_ps256 ->> _mm256_permute_ps
Комментарии:
1.
_mm256_permute_ps
является внутренним дляvpermilps
, потому что Intel недальновидно называла свои внутренние компоненты, используя «перестановку» для этой инструкции AVX1, а затем пришлось использовать_mm256_permutexvar_ps
aka_mm256_permutevar8x32_ps
дляvpermps
того, чтобы однажды появился AVX2. Но да, это наиболее эффективный способ, если у вас есть AVX2, особенно если вы можете загрузить вектор управления перемешиванием только один.2. В противном случае с AVX1, я думаю
VBROADCASTF128
/_mm_permutevar_ps
(vpermilps ymm, ymm, ymm с векторным управлением). На самом деле да, это лучше, потому что широковещательная загрузка так же дешева, как и обычная загрузка, а перетасовка в полосе движения обеспечивает меньшую задержку и быстрее на Zen 1.3. Я все еще не понимаю, как работает permute после прочтения документов Intels. Но эта ссылка ниже действительно помогает. codeproject.com/Articles/874396/…
4.@Noob: на самом деле это
_mm256_permute_ps
не работает для этого; у этого ответа была правильная идея, но он использовал неправильный внутренний дляvpermps
.__m256 _mm256_permute_ps (__m256 a, int control);
является внутренним дляvpermilps
с непосредственным управляющим операндом (одинаковое перемешивание в каждой 128-битной полосе) ( felixcloutier.com/x86/vpermilps).5. @PeterCordes ты прав! _mm256_permutevar_ps должен выполнить эту работу