Как сказать GCC, что аргумент-указатель всегда выровнен по двойному слову? - PullRequest
25 голосов
/ 07 марта 2012

В моей программе есть функция, которая делает простое сложение векторов 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 ]

10 голосов
/ 09 марта 2012

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

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

10 голосов
/ 08 марта 2012

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

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

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

6 голосов
/ 27 марта 2017

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

2 голосов
/ 08 марта 2012

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;
}
1 голос
/ 08 марта 2012

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

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

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

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

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

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

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

0 голосов
/ 08 марта 2012

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

...