如何告诉GCC指针参数总是双字对齐?

在我的程序中,我有一个函数,它做一个简单的向量加法c[0:15] = a[0:15] + b[0:15] 。 function原型是:

 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)属性,但它不会反映在函数代码本身中。 是否可以将此属性添加到函数参数中?

根据我在我的系统上找到的一段示例代码,我尝试了以下解决方案,其中包含了前面给出的一些答案的想法:基本上,创建一个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指针是对齐的。 从那以后它是否符合您的要求取决于编译器是否可以有效地使用该信息; 它可能不够智能:这些优化并不容易。

另一种选择可能是将float包装在一个必须是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); 

意味着编译器可以假设设置为arg的x至少是16字节对齐的,而:

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

意味着编译器可以假设为x,设置为arg,(char *)x – 8是32字节对齐的。

基于Stack Overflow大约2010年的一些其他问题和答案,看起来内置在GCC 3和早期GCC 4中不可用。但我不知道截止点在哪里。

gcc版本对简单的typedef和数组的align()很狡猾。 通常,要执行您想要的操作,您必须将float包装在struct中,并使包含的float具有对齐限制。

使用运算符重载几乎可以使这无痛,但它确实假设您可以使用c ++语法。

 #include  #include  #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__放在哪里)

并在整个代码中使用该类型。 对于你的function定义,我试试

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

这为您提供了额外的间接,但这只是语法。 像*a这样的东西只是一个noop,只能将指针重新解释为指向第一个元素的指针。

我从来没用过它,但有_ 属性 _((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