Нет, использование float *__attribute__((aligned(32))) x
означает, что сам указатель хранится в выровненной памяти, не указывая на выровненную память. 1
Есть способ сделать это, но он помогает только для gcc, а не для clang или ICC.
См. Как сообщить GCC, что аргумент указателя всегда выровнен по двойному слову? для __builtin_assume_aligned
, который работает на всех компиляторах, совместимых с GNU C, и Как применить __attribute __ (( выровненный (32))) к int *? для более подробной информации о __attribute__((aligned(32)))
, который работает для GCC.
Я использовал __restrict
вместо __restrict__
, поскольку это расширение C ++ для C99 restrict
переносимо для всех основных компиляторов x86 C ++, включая MSVC.
typedef float aligned32_float __attribute__((aligned(32)));
void prod(const aligned32_float * __restrict x,
const aligned32_float * __restrict y,
int size,
aligned32_float* __restrict out0)
{
size &= -16ULL;
#if 0 // this works for clang, ICC, and GCC
x = (const float*)__builtin_assume_aligned(x, 32); // have to cast the result in C++
y = (const float*)__builtin_assume_aligned(y, 32);
out0 = (float*)__builtin_assume_aligned(out0, 32);
#endif
for (int i = 0; i < size; ++i) {
out0[i] = x[i] * y[i]; // auto-vectorized with a memory operand for mulps
// note clang using two separate movups loads
// instead of a memory operand for mulps
}
}
( вывод gcc, clang и ICC в проводнике компилятора Godbolt ).
GCC и clang будут использовать movaps
/ vmovaps
вместо ups
каждый раз, когда у них есть гарантия выравнивания во время компиляции. (В отличие от MSVC и ICC, которые никогда не используют movaps
для загрузки / хранения, пропущенная оптимизация для всего, что работает на Core2 / K10 или старше). И, как вы заметили, он применяет эффекты -mavx256-split-unaligned-load
/ store
к настройкам, отличным от Haswell ( Почему gcc не разрешает _mm256_loadu_pd как один vmovupd? ). Это еще одна подсказка, которую не использовал ваш синтаксис работа.
vmovups
не является проблемой производительности при использовании в выровненной памяти; он работает идентично vmovaps
на всех процессорах, поддерживающих AVX, когда адрес выровнен во время выполнения. Так что на практике нет реальной проблемы с вашим -march=haswell
выводом. Только старые процессоры, до Nehalem и Bulldozer, всегда декодировали movups
на несколько мопов.
Реальным преимуществом (в наши дни) рассказа компилятору о гарантиях выравнивания является то, что компиляторы иногда генерируют дополнительный код для циклов запуска / очистки, чтобы достичь границы выравнивания. Или без AVX компиляторы не могут сложить загрузку в операнд памяти за mulps
, если он не выровнен.
Хорошим примером для этого является out0[i] = x[i] * y[i]
, где результат загрузки требуется только один раз. или out0[i] *= x[i]
. Знание выравнивания позволяет movaps
/ mulps xmm0, [rsi]
, иначе это 2x movups
+ mulps
. Вы можете проверить эту оптимизацию даже на таких компиляторах, как ICC или MSVC, которые используют movups
, даже если они do знают, что у них есть гарантия выравнивания, но они все равно будут создавать код, необходимый для выравнивания, когда они могут сложить загрузить в операцию ALU.
Кажется, __builtin_assume_aligned
- единственный действительно переносимый (для компиляторов GNU C) способ сделать это . Вы можете делать хаки, такие как передача указателей на struct aligned_floats { alignas(32) float f[8]; };
, но это просто громоздко в использовании, и если вы не получите доступ к памяти через объекты этого типа, компиляторы не получат выравнивание. (например, приведение указателя обратно к float *
Я пытаюсь использовать одно чтение и много записи для насыщения портов процессора для записи.
Использование более 4 выходных потоков может повредить, что приведет к большему количеству пропусков конфликтов в кеше. Кеш L2 Skylake, например, только 4-сторонний. Но L1d 8-полосный, так что вы, вероятно, в порядке для небольших буферов.
Если вы хотите насытить пропускную способность порта хранилища, используйте более узкие хранилища (например, скалярные), а не широкие хранилища SIMD, для которых требуется больше пропускной способности на моп. Резервные хранилища в одной и той же строке кэша могут быть объединены в буфере хранилища перед фиксацией в L1d, поэтому это зависит от того, что вы хотите протестировать.
Полусвязанный: шаблон доступа к памяти в 2x нагрузке + 1x, такой как c[i] = a[i]+b[i]
или триада STREAM, будет ближе всего к максимальной загрузке кэша L1d + пропускной способности хранилища на процессорах семейства Intel Sandybridge. В SnB / IvB 256-битные векторы занимают 2 цикла на загрузку / хранилище, оставляя время для хранения адресов адресов для использования AGU на портах 2 или 3 во время 2-го цикла загрузки. В Haswell и более поздних версиях (порты загрузки / хранения 256-битной ширины) хранилищам необходимо использовать неиндексированный режим адресации, чтобы они могли использовать AGU хранилища в режиме простой адресации на порту 7.
Но процессоры AMD могут выполнять до 2 операций с памятью за такт, при этом самое большее один из них является хранилищем, поэтому они будут максимально использовать шаблон «копировать и работать» = загружать.
Кстати, Intel недавно анонсировала Sunny Cove (преемник Ice Lake), который будет иметь 2x нагрузка + 2x пропускная способность магазина за такт, ALU 2-го вектора в случайном порядке и выпуск / переименование шириной 5. Так что это весело! Компиляторам потребуется развернуть циклы как минимум на 2, чтобы не создавать узких мест в ветвях циклов 1 на такт.
Сноска 1 : Вот почему (если вы компилируете без AVX), вы получаете предупреждение, а gcc пропускает and rsp,-32
, поскольку предполагает, что RSP уже выровнен. (На самом деле он не проливает никаких регистров YMM, так что в любом случае следовало бы оптимизировать это, но у gcc некоторое время была ошибка с пропущенной оптимизацией с локальными объектами или объектами, созданными автоматическими векторизациями с дополнительным выравниванием.)
<source>:4:6: note: The ABI for passing parameters with 32-byte alignment has changed in GCC 4.6