Как вы загружаете/сохраняете из/в массив удвоений с помощью векторных расширений GNU C?

Я использую GNU C Vector Extensions, а не Intel _mm_* intrinsics.

Я хочу сделать то же самое, что и Intel _m256_loadu_pd intrinsic. Присвоение значений один за другим происходит медленно: gcc создает код, который имеет 4 команды загрузки, а не один vmovupd (который _m256_loadu_pd создает).

typedef double vector __attribute__((vector_size(4 * sizeof(double))));

int main(int argc, char **argv) {
    double a[4] = {1.0, 2.0, 3.0, 4.0};
    vector v;

    /* I currently do this */
    v[0] = a[0];
    v[1] = a[1];
    v[2] = a[2];
    v[3] = a[3];
}

Мне нужно что-то вроде этого:

v = (vector)(a);

или

v = *((vector*)(a));

но не работают. Первый сбой "не может преобразовать значение в вектор", а второй - в segfaults.

Ответ 1

update: Я вижу, что вы используете собственный векторный синтаксис GNU C, а не встроенные функции Intel. Вы избегаете встроенных функций Intel для переносимости на не-x86? gcc в настоящее время выполняет код компиляции с плохой работой, который использует векторы GNU C шире, чем поддерживает целевая машина. (Вы надеетесь, что он просто будет использовать два 128b вектора и работать по каждому отдельно, но, видимо, это хуже, чем это.)

В любом случае этот ответ показывает, как вы можете использовать встроенные функции Intel x86 для загрузки данных в векторные синтаксисы GNU C


Прежде всего, просмотр вывода компилятора менее чем -O2 - это пустая трата времени, если вы пытаетесь узнать что-либо о том, что будет скомпилировано для хорошего кода. Ваш main() будет оптимизирован только для ret на -O2.

Кроме того, не удивительно, что вы получаете плохие asm от назначения элементов вектора по одному.


Кроме того, обычные люди будут называть тип v4df (вектор 4 Double Float) или что-то, а не vector, поэтому они не сходят с ума при использовании его с С++ std::vector. Для одной точности v8sf. IIRC, gcc использует имена типов, подобные этому внутри, для __m256d.

На x86 встроенные типы Intel (например, __m256d) реализованы поверх векторного синтаксиса GNU C (поэтому вы можете сделать v1 * v2 в GNU C вместо записи _mm256_mul_pd(v1, v2)). Вы можете свободно конвертировать из __m256d в v4df, как я сделал здесь.

Я обернул оба разумных способа сделать это в функциях, поэтому мы можем посмотреть их asm. Обратите внимание, как мы не загружаемся из массива, который мы определяем внутри одной и той же функции, поэтому компилятор не будет оптимизировать его.

Я помещал их в Godbolt explorer, чтобы вы могли посмотреть на asm с различными параметрами компиляции и версиями компилятора.

typedef double v4df __attribute__((vector_size(4 * sizeof(double))));

#include <immintrin.h>

// note the return types.  gcc6.1 compiles with no warnings, even at -Wall -Wextra
v4df load_4_doubles_intel(const double *p) { return _mm256_loadu_pd(p); }
    vmovupd ymm0, YMMWORD PTR [rdi]   # tmp89,* p
    ret

v4df avx_constant() { return _mm256_setr_pd( 1.0, 2.0, 3.0, 4.0 ); }
    vmovapd ymm0, YMMWORD PTR .LC0[rip]
    ret

Если аргументы args для _mm_set* не являются константами времени компиляции, компилятор сделает все возможное, чтобы сделать эффективный код, чтобы все элементы были включены в один вектор. Обычно лучше всего это делать, а не писать C, который хранится в tmp-массиве и загружается из него, потому что это не всегда лучшая стратегия. (Ошибка хранилища в нескольких узких хранилищах, пересылаемых на широкую нагрузку, требует дополнительных ~ 10 циклов (IIRC) задержек в дополнение к обычной задержке пересылки. Если ваш double уже находится в регистре, обычно лучше всего просто перемешайте их вместе.)


См. также Можно ли прикладывать float непосредственно к __m128, если они имеют 16 байтов с привязкой? для списка различных свойств для получения одного скаляра в вектор, тег wiki имеет ссылки на руководства Intel, и их искатель intrinsics.


Загрузка/сохранение векторов GNU C без встроенных функций Intel:

Я не уверен, как вы "предположили" это сделать. Этот Q & A предлагает предлагать указатель на память, которую вы хотите загрузить, и использовать векторный тип типа typedef char __attribute__ ((vector_size (16),aligned (1))) unaligned_byte16; (обратите внимание на атрибут aligned(1)).

Вы получаете segfault от *(v4df *)a, потому что предположительно a не выравнивается по 32-байтовой границе, но вы используете векторный тип, который предполагает естественное выравнивание. (Точно так же, как __m256d, если вы разыскиваете указатель на него, вместо того, чтобы использовать load/store intrinsics для передачи информации о выравнивании компилятору.)

Ответ 3

Если вам не нужна копия a, используйте вместо этого указатель (см. пример v_ptr). Если вам нужна копия, используйте memmove (см. V_copy)

#include <stdio.h>
#include <string.h>

typedef double vector __attribute__((vector_size(4 * sizeof(double))));

int main(int argc, char **argv) {
  double a[4] = {1.0, 2.0, 3.0, 4.0};
  vector *v_ptr;
  vector v_copy;

  v_ptr = (vector*)&a;
  memmove(&v_copy, a, sizeof(a));

  printf("a[0] = %f // v[0] = %f // v_copy[0] = %f\n", a[0], (*v_ptr)[0], v_copy[0]);
  printf("a[2] = %f // v[2] = %f // v_copy[0] = %f\n", a[2], (*v_ptr)[2], v_copy[2]);
  return 0;
}

выход:

a[0] = 1.000000 // v[0] = 1.000000 // v_copy[0] = 1.000000
a[2] = 3.000000 // v[2] = 3.000000 // v_copy[0] = 3.000000