在我的程序中,我有一个函数可以执行简单的向量加法
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)属性,但它并没有反映在函数代码本身中。是否可以将此属性添加到函数参数中?
如果属性不起作用,或者不是一个选项......
我不确定,但试试这个:
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 字节对齐,但同样,我不知道编译器是否足够聪明来使用它。
按照我在系统上找到的一段示例代码,我尝试了以下解决方案,其中结合了之前给出的一些答案的想法:基本上,创建一个 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()
将是更好的解决方案,可以防止石膏和可能的副作用,如果它有效的话......
编辑:我注意到内置函数实际上在我们的实现中存在错误(即,它不仅不起作用,而且会导致代码稍后出现计算错误。
如何告诉 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 字节对齐的。
根据 2010 年左右 Stack Overflow 上的一些其他问题和答案,似乎内置功能在 GCC 3 和早期的 GCC 4 中不可用。但我不知道截止点在哪里。
对齐规范通常仅适用于小于指针基本类型的对齐,而不是更大。
我认为最简单的方法是使用对齐规范声明整个数组,例如
typedef float myvector[16];
typedef myvector alignedVector __attribute__((aligned (8));
(语法可能不正确,我总是很难知道把这些
__attribute__
放在哪里)
并在整个代码中使用该类型。对于你的函数定义,我会尝试
void vecadd(alignedVector * restrict a, alignedVector * restrict b, alignedVector * restrict c);
这为您提供了额外的间接寻址,但这只是语法。像
*a
这样的东西只是一个空操作,仅将指针重新解释为指向第一个元素的指针。
gcc 版本对于简单类型定义和数组上的align() 一直很狡猾。通常,要执行您想要的操作,您必须将浮点数包装在结构中,并使包含的浮点数具有对齐限制。
通过运算符重载,您几乎可以轻松做到这一点,但它确实假设您可以使用 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;
}
我没用过,但是有_属性_((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