Как сказать 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. Есть ли модификатор, который нужно добавить к объявлению указателя, чтобы сообщить об этом компилятору?

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

6 ответов

Решение

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

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() было бы предпочтительным решением, предотвращая приведение и возможные побочные эффекты, если бы это только сработало...

РЕДАКТИРОВАТЬ: я заметил, что встроенная функция на самом деле глючит в нашей реализации (то есть, не только она не работает, но она вызывает ошибки вычисления позже в коде.

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

Я не уверен, но попробуйте это:

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, что указатели выровнены. От того, будет ли он делать то, что вы хотите, зависит от того, сможет ли компилятор эффективно использовать эту информацию; это может быть недостаточно умно: эти оптимизации не легки.

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

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

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

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

Как сказать 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. Но я не знаю, где находится точка отсечения.

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

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

#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;
}

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

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

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 и только интерпретирует указатель как указатель на первый элемент.

Я никогда не использовал его, но есть _атрибут_((выровненный (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

Другие вопросы по тегам