Вам нужно vmovdqa32
, потому что AVX512 имеет маскировку для каждого элемента; все инструкции должны иметь размер элемента SIMD. Ниже приведена версия, которая должна быть безопасной. Вы бы видели это, если бы прочитали руководство для vmovdqa
; vmovdqa32
для ZMM задокументировано в той же записи.
(3): код ядра компилируется с отключенным SSE / AVX, поэтому компилятор никогда не будет генерировать инструкции, которые касаются xmm / регистры ymm / zmm. (для большинства ядер, например, Linux). Вот что делает этот код «безопасным» от изменения регистра между операторами asm. Хотя по-прежнему плохая идея делать их отдельными операторами для этого варианта использования, несмотря на то, что код Linux md-raid делает это. OTOH, позволяющий компилятору планировать некоторые другие инструкции между хранилищем и загрузкой, не является плохой вещью.
Порядок между asm
операторами обеспечивается ими обоими, являющимися volatile
- компиляторы не могут переупорядочивать изменчивые операции с другими энергозависимые операции, только с простыми операциями.
В Linux, например, безопасно использовать инструкции FP / SIMD только между вызовами kernel_fpu_begin()
и kernel_fpu_end()
(которые являются медленными : begin сохраняет все SIMD-состояние на месте, а end восстанавливает его или, по крайней мере, помечает как необходимое, прежде чем вернуться в пользовательское пространство). Если вы ошиблись, ваш код будет молча повреждать векторные регистры пространства пользователя !!
Это должно использоваться в модуле ядра, скомпилированном с EXTRA_CFLAGS + = -mavx2 - mavx512f для поддержки AVX-512.
Вы не должны этого делать. Разрешение компилятору выдавать свои собственные инструкции AVX / AVX512 в коде ядра может иметь катастрофические последствия, поскольку вы не можете остановить это от уничтожения вектора рег до kernel_fpu_begin()
. Используйте только векторные регистры через встроенный asm.
Также обратите внимание, что использование регистров ZMM вообще временно снижает максимальную частоту турбо тактовой частоты для этого ядра (или на "клиентском" чипе, для все ядра, потому что их тактовые частоты заблокированы вместе). См. SIMD инструкции по снижению частоты процессора
Я хотел бы использовать 512b zmm * регистров в качестве хранилища памяти.
С быстрым L1d-кешем и переадресация хранилища, вы уверены, что что-нибудь выиграете от использования регистров ZMM в качестве быстрого «подобного памяти» (локального для потока) хранилища? Особенно, когда вы можете получать данные только из SIMD-регистров и возвращать их в целочисленные регистры с помощью сохранения / перезагрузки из массива (или более встроенного asm для перемешивания ...). В некоторых местах в Linux (например, md
RAID5 / RAID6) используются инструкции SIMD ALU для контроля четности блоков XOR или raid6, и это стоит затрат kernel_fpu_begin()
. Но если вы просто загружаете / сохраняете, чтобы использовать состояние ZMM / YMM в качестве хранилища, которое не может пропустить кеш, не зацикливаясь на больших буферах, это, вероятно, не стоит.
( Редактировать: оказывается, что вы действительно хотите использовать 64-байтовые копии для генерации транзакций P CIe, что является совершенно отдельным вариантом использования, чем хранение данных в регистрах в течение длительного времени.)
Если вы просто хотите скопировать 64 байта с загрузкой одной инструкции
Как вы, по-видимому, на самом деле делаете, чтобы получить 64-байтовую транзакцию P CIe.
It было бы лучше сделать это одним оператором asm, потому что в противном случае нет никакой связи между двумя операторами asm, кроме того, что оба asm volatile
заставляют этот порядок. (Если бы вы делали это с инструкциями AVX, включенными для использования компилятором, вы бы просто использовали встроенные функции, а не "=x"
/ "x"
выходы / входы для соединения отдельных операторов asm.)
Почему пример выбрал ymm1? Как и любой другой случайный выбор ymm0..7 для разрешения 2-байтового префикса VEX (ymm8..15 может потребоваться больший размер кода в этих инструкциях.) При отключенном AVX code-gen нет способа попросить компилятор выбрать удобный регистр для вас с фиктивным операндом.
uint8_t datareg[32];
не работает; это должно быть alignas(32) uint8_t datareg[32];
, чтобы гарантировать, что vmovdqa
хранилище не выйдет из строя.
Clobber "memory"
на выходе бесполезен; весь массив уже является выходным операндом, потому что вы назвали переменную массива в качестве выходного, а не просто указатель. (Фактически, приведение к указателю на массив - это то, как вы говорите компилятору, что ввод или вывод простого разыменованного указателя на самом деле шире, например, для asm, который содержит циклы, или в этом случае для asm, который использует SIMD, когда мы не можем сообщить компилятору о векторах. Как я могу указать, что можно использовать память, * указанную * встроенным аргументом ASM? )
Оператор asm
является изменчивым, поэтому он выиграл не оптимизировать, чтобы использовать тот же результат. Единственный объект C, которого касается оператор asm, это объект массива, который является выходным операндом, поэтому компиляторы уже знают об этом эффекте.
Версия AVX512:
AVX512 имеет -элементное маскирование как часть любой инструкции, в том числе загрузки / хранения. Это означает, что есть vmovdqa32
и vmovdqa64
для различной степени детализации маскирования. (И vmovdqu8/16/32/64
, если вы включите AVX512BW). Версии инструкций FP уже содержат ps или pd для мнемони c, поэтому мнемони c остаются неизменными для векторов ZMM. Вы увидите это сразу, если посмотрите на сгенерированный компилятором asm для автоматически векторизованного l oop с 512-битными векторами или встроенными функциями.
Это должно быть безопасно:
#include <stdalign.h>
#include <stdint.h>
#include <string.h>
#define __force
int foo (void *addr) {
alignas(16) uint8_t datareg[64]; // 16-byte alignment doesn't cost any extra code.
// if you're only doing one load per function call
// maybe not worth the couple extra instructions to align by 64
asm volatile (
"vmovdqa32 %1, %%zmm16\n\t" // aligned
"vmovdqu32 %%zmm16, %0" // maybe unaligned; could increase latency but prob. doesn't hurt throughput much compared to an IO read.
: "=m"(datareg)
: "m" (*(volatile const char (* __force)[64]) addr) // the whole 64 bytes are an input
: // "memory" not needed, except for ordering wrt. non-volatile accesses to other memory
);
int retval;
memcpy(&retval, datareg+8, 4); // memcpy can inline as long as the kernel doesn't use -fno-builtin
// but IIRC Linux uses -fno-strict-aliasing so you could use cast to (int*)
return retval;
}
Компилируется в проводнике компилятора Godbolt с gcc -O3 -mno-sse
до
foo:
vmovdqa32 (%rdi), %zmm16
vmovdqu32 %zmm16, -72(%rsp)
movl -64(%rsp), %eax
ret
Я не знаю, как определяется ваш __force
; это может быть go перед addr
вместо типа указателя массива. Или, может быть, это часть типа элемента массива volatile const char
. Снова, см. Как я могу указать, что можно использовать память, * указанную * встроенным аргументом ASM? для получения дополнительной информации об этом вводе.
Поскольку вы читаете память ввода-вывода, asm volatile
необходимо; другое чтение того же адреса может прочитать другое значение. То же самое, если вы читали память, которую другое ядро ЦП могло бы изменить асинхронно.
В противном случае я думаю, что asm volatile
не требуется, если вы хотите, чтобы компилятор оптимизировал выполнение той же копии.
A "memory"
clobber также не требуется: мы сообщаем компилятору о полной ширине как ввода, так и вывода, поэтому он имеет полное представление о том, что происходит.
Если вам нужно порядок заказа другие не volatile
обращения к памяти, вы можете использовать для этого "memory"
clobber. Но asm volatile
упорядочен по отношению к. разыменование указателей volatile
, включая READ_ONCE и WRITE_ONCE, которые вы должны использовать для любого межпоточного взаимодействия без блокировки (при условии, что это ядро Linux).
ZMM16..31 не требует vzeroupper, чтобы избежать проблем с производительностью, а EVEX всегда имеет фиксированную длину.
Я выровнял выходной буфер только на 16 байтов. Если есть фактический вызов функции, который не становится встроенным для каждой 64-байтовой загрузки, накладные расходы на выравнивание RSP на 64 могут быть больше, чем стоимость хранилища с разделением строки кэша в 3/4 времени. Переадресация из хранилища, я думаю, все еще эффективно работает из этого широкого хранилища, чтобы сузить перезагрузки фрагментов этого буфера на процессорах семейства Skylake-X.
Если вы читаете в больший буфер, используйте его для вывода вместо того, чтобы прыгать через 64-байтовый массив tmp.
Возможно, есть другие способы генерирования более широких P CIe транзакций чтения ; если память находится в области W C, то также должны работать 4x movntdqa
загрузки из того же выровненного 64-байтового блока. Или 2х vmovntdqa ymm
нагрузок; Я бы порекомендовал, чтобы избежать турбо штрафов.