Как сообщить GCC, что аргумент указателя всегда совмещен с двойным словом?

В моей программе у меня есть функция, которая делает простое добавление вектора c[0:15] = a[0:15] + b[0:15]. Прототип функции:

void vecadd(float * restrict a, float * restrict b, float * restrict c);

В нашей 32-битной встроенной архитектуре есть опция загрузки/хранения загрузки/хранения двойных слов, например:

r16 = 0x4000  ;
strd r0,[r16] ; stores r0 in [0x4000] and r1 in [0x4004]

Оптимизатор GCC распознает векторный характер цикла и генерирует две ветки кода: один для случая, когда 3 массива выравниваются по двойному слову (поэтому он использует инструкции двойной загрузки/хранения), а другой для случая что массивы выравниваются по слову (где он использует параметр одиночного загрузки/хранения).

Проблема заключается в том, что проверка выравнивания адресов является дорогостоящей относительно части добавления, и я хочу ее устранить, намекая компилятору, что a, b и c всегда выравниваются по 8. Есть ли модификатор для добавления в объявление указателя, чтобы сообщить об этом компилятору?

Массивы, которые используются для вызова этой функции, имеют атрибут aligned (8), но он не отражается в самом коде функции. можно ли добавить этот атрибут к параметрам функции?

Ответ 1

Следуя примеру кода, который я нашел в своей системе, я попробовал следующее решение, которое включает в себя идеи из нескольких ответов, приведенных ранее: в принципе, создайте объединение небольшого массива поплавков с 64- разрядный тип - в этом случае SIMD-вектор с плавающей запятой - и вызывает функцию с литой массивов float-операндов:

typedef float f2 __attribute__((vector_size(8)));
typedef union { f2 v; float f[2]; } simdfu;

void vecadd(f2 * restrict a, f2 * restrict b, f2 * restrict c);

float a[16] __attribute__((aligned(8)));
float b[16] __attribute__((aligned(8)));
float c[16] __attribute__((aligned(8)));

int main()
{
    vecadd((f2 *) a, (f2 *) b, (f2 *) c);
    return 0;
}

Теперь компилятор не создает ветвь с 4-ю строками.

Тем не менее, __builtin_assume_aligned() было бы предпочтительным решением, предотвращающим приведение и возможные побочные эффекты, если бы он работал только...

EDIT: Я заметил, что встроенная функция на самом деле ошибочна в нашей реализации (т.е. не только она не работает, но и вызывает ошибки вычисления позже в коде.

Ответ 2

Если атрибуты не работают или не являются параметрами....

Я не уверен, но попробуйте следующее:

void vecadd (float * restrict a, float * restrict b, float * restrict c)
{
   a = __builtin_assume_aligned (a, 8);
   b = __builtin_assume_aligned (b, 8);
   c = __builtin_assume_aligned (c, 8);

   for ....

Это должно указывать GCC, что указатели выровнены. Из того, будет ли он делать то, что вы хотите, зависит от того, сможет ли компилятор эффективно использовать эту информацию; он может быть недостаточно умен: эти оптимизации нелегки.

Другой вариант может заключаться в том, чтобы обернуть float внутри объединения, которое должно быть выровнено по 8 байт:

typedef union {
  float f;
  long long dummy;
} aligned_float;

void vedadd (aligned_float * a, ......

Я думаю, что это должно обеспечить 8-байтовое выравнивание, но опять же, я не знаю, достаточно ли компилятор, чтобы использовать его.

Ответ 3

Как сообщить GCC, что аргумент указателя всегда совмещен с двойным словом?

Похоже, что более новые версии GCC имеют __builtin_assume_aligned:

Встроенная функция: void * __builtin_assume_aligned (const void *exp, size_t align, ...)

Эта функция возвращает свой первый аргумент и позволяет компилятору предположить, что возвращенный указатель, по меньшей мере, выравнивает байты. Этот встроенный может иметь два или три аргумента, если он имеет три, третий аргумент должен иметь целочисленный тип, а если он отличен от нуля означает смещение смещения. Например:

void *x = __builtin_assume_aligned (arg, 16);

означает, что компилятор может считать x, установленным в arg, не менее 16-байтовым, а:

void *x = __builtin_assume_aligned (arg, 32, 8);

означает, что компилятор может принять за x, установить в arg, что (char *) x - 8 выровнен по 32 байт.

Основываясь на некоторых других вопросах и ответах о переполнении стека около 2010 года, кажется, что встроенный не был доступен в GCC 3 и раннем GCC 4. Но я не знаю, где точка отсечения.

Ответ 4

Варианты gcc были изволены относительно align() для простых typedefs и массивов. Обычно, чтобы делать то, что вы хотите, вам нужно было бы поместить float в структуру и иметь ограниченный поплавок с ограничением выравнивания.

С перегрузкой оператора вы можете почти сделать это безболезненным, но он предполагает, что вы можете использовать синтаксис С++.

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

#define restrict __restrict__

typedef float oldfloat8 __attribute__ ((aligned(8)));

struct float8
{
    float f __attribute__ ((aligned(8)));

    float8 &operator=(float _f) { f = _f; return *this; }
    float8 &operator=(double _f) { f = _f; return *this; }
    float8 &operator=(int _f) { f = _f; return *this; }

    operator float() { return f; }
};

int Myfunc(float8 * restrict a, float8 * restrict b, float8 * restrict c);

int MyFunc(float8 * restrict a, float8 * restrict b, float8 * restrict c)
{
    return *c = *a* *b;
}

int main(int argc, char **argv)
{
    float8 a, b, c;

    float8 p[4];

    printf("sizeof(oldfloat8) == %d\n", (int)sizeof(oldfloat8));
    printf("sizeof(float8) == %d\n", (int)sizeof(float8));

    printf("addr p[0] == %p\n", &p[0] );
    printf("addr p[1] == %p\n", &p[1] );

    a = 2.0;
    b = 7.0;
    MyFunc( &a, &b, &c );
    return 0;
}

Ответ 5

Спецификации выравнивания обычно работают только для выравниваний, которые меньше основного типа указателя, но не больше.

Мне кажется, проще всего объявить весь массив с помощью спецификации выравнивания, что-то вроде

typedef float myvector[16];
typedef myvector alignedVector __attribute__((aligned (8));

(Синтаксис может быть неправильным, мне всегда трудно понять, где их поставить __attribute__ s)

И используйте этот тип во всем коде. Для определения функции я бы попробовал

void vecadd(alignedVector * restrict a, alignedVector * restrict b, alignedVector * restrict c);

Это дает вам дополнительную косвенность, но это только синтаксис. Что-то вроде *a является просто noop и только переинтерпретирует указатель как указатель на первый элемент.

Ответ 6

Я никогда не использовал его, но есть _attribute _ ((aligned (8)))

Если я правильно прочитал документацию, он будет использоваться следующим образом:

void vecadd(float * restrict a __attribute__((aligned (8))), 
            float * restrict b __attribute__((aligned (8))), 
            float * restrict c __attribute__((aligned (8))));

см. http://ohse.de/uwe/articles/gcc-attributes.html#type-aligned