Как загрузить регистр avx-512 zmm с адреса ioremap ()? - PullRequest
1 голос
/ 16 марта 2020

Моя цель - создать транзакцию P CIe с полезной нагрузкой более 64b. Для этого мне нужно прочитать адрес ioremap().

Для 128b и 256b я могу использовать регистры xmm и ymm соответственно, и это работает как ожидалось.

Теперь я хотел бы сделать то же самое для регистров 512b zmm (память как хранилище?!)

Код по лицензии, который я не могу показывать здесь, использует ассемблерный код для 256b:

void __iomem *addr;
uint8_t datareg[32];
[...]
// Read memory address to ymm (to have 256b at once):
asm volatile("vmovdqa %0,%%ymm1" : : "m"(*(volatile uint8_t * __force) addr));
// Copy ymm data to stack data: (to be able to use that in a gcc handled code)
asm volatile("vmovdqa %%ymm1,%0" :"=m"(datareg): :"memory");

Это должно использоваться в * Модуль 1014 * kernel скомпилирован с EXTRA_CFLAGS += -mavx2 -mavx512f для поддержки AVX-512 . edit: Чтобы проверить во время компиляции, поддерживаются ли __AVX512F__ и __AVX2__.

  1. Почему в этом примере используется ymm1, а не другой регистр ymm0-2-3-4..15?
  2. Как я могу прочитать адрес в регистр 512b zmm?
  3. Как я могу быть уверен, что регистр не будет перезаписан между двумя asm строками?

Простая замена ymm на zmm, g cc показывает Error: operand size mismatch for vmovdqa'`.

Если этот код неверен или рекомендуется Позвольте мне сначала решить это, так как я только начал копаться в этом.

1 Ответ

3 голосов
/ 16 марта 2020

Вам нужно 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 нагрузок; Я бы порекомендовал, чтобы избежать турбо штрафов.

...