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

ndasle7k  于 11个月前  发布在  其他
关注(0)|答案(6)|浏览(125)

在我的程序中,我有一个简单的向量加法函数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)属性,但它没有反映在函数代码本身中。是否可以将此属性添加到函数参数中?

xdnvmnnf

xdnvmnnf1#

如果属性不起作用,或者没有选择....
我不确定,但试试这个:

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指针是对齐的。从这一点来看,它是否做你想要的取决于编译器是否能有效地使用这些信息;它可能不够聪明:这些优化并不容易。
另一种选择可能是将浮点数 Package 在一个必须8字节对齐的联合体中:

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

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


我认为应该强制8字节对齐,但同样,我不知道编译器是否足够聪明,可以使用它。

j5fpnvbx

j5fpnvbx2#

根据我在系统上找到的一段示例代码,我尝试了以下解决方案,它结合了前面给出的一些答案的想法:基本上,创建一个具有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()将是更可取的解决方案,防止演员和可能的副作用,如果它只工作.
编辑:我注意到,内置函数在我们的实现中实际上是有缺陷的(即,它不仅不工作,而且会导致代码后面的计算错误。

lp0sw83n

lp0sw83n3#

如何告诉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年左右Stack Overflow上的一些其他问题和答案,似乎内置的在GCC 3和早期的GCC 4中不可用。但我不知道分界点在哪里。

bmp9r5qi

bmp9r5qi4#

对方式规范通常只适用于小于指针基类型的对方式,而不适用于大于指针基类型的对方式。
我认为最简单的方法是用对齐规范声明整个数组,

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

字符串
(The语法可能不正确,我总是很难知道把这些__attribute__放在哪里)
并在整个代码中使用该类型。

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


这给了你一个额外的间接,但这只是语法。像*a只是一个noop,只把指针重新解释为指向第一个元素的指针。

axr492tv

axr492tv5#

gcc版本在简单类型定义和数组上的align()是不可靠的。通常要做你想做的事情,你必须把浮点数 Package 在一个结构体中,并让包含的浮点数有对齐限制。
使用运算符重载,你几乎可以轻松地做到这一点,但它确实假设你可以使用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;
}

字符串

kninwzqo

kninwzqo6#

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

相关问题