告诉 C++ 指针数据是 16 字节对齐的
Tell C++ that pointer data is 16 byte aligned
我用静态数组写了一些代码,它矢量化得很好。
float data[1024] __attribute__((aligned(16)));
我想动态分配数组。我试过这样做:
float *data = (float*) aligned_alloc(16, size*sizeof(float));
但是编译器 (GCC 4.9.2) 无法再对代码进行矢量化。我假设这是因为它不知道指针数据是 16 字节对齐的。我收到如下消息:
note: Unknown alignment for access: *_43
我试过在使用数据之前添加这一行,但它似乎没有做任何事情:
data = (float*) __builtin_assume_aligned(data, 16);
使用不同的变量 restrict
没有帮助:
float* __restrict__ align_data = (float*) __builtin_assume_aligned(data,16);
示例:
#include <iostream>
#include <stdlib.h>
#include <math.h>
#define SIZE 1024
#define DYNAMIC 0
#define A16 __attribute__((aligned(16)))
#define DA16 (float*) aligned_alloc(16, size*sizeof(float))
class Test{
public:
int size;
#if DYNAMIC
float *pos;
float *vel;
float *alpha;
float *k_inv;
float *osc_sin;
float *osc_cos;
float *dosc1;
float *dosc2;
#else
float pos[SIZE] A16;
float vel[SIZE] A16;
float alpha[SIZE] A16;
float k_inv[SIZE] A16;
float osc_sin[SIZE] A16;
float osc_cos[SIZE] A16;
float dosc1[SIZE] A16;
float dosc2[SIZE] A16;
#endif
Test(int arr_size){
size = arr_size;
#if DYNAMIC
pos = DA16;
vel = DA16;
alpha = DA16;
k_inv = DA16;
osc_sin = DA16;
osc_cos = DA16;
dosc1 = DA16;
dosc2 = DA16;
#endif
}
void compute(){
for (int i=0; i<size; i++){
float lambda = .67891*k_inv[i],
omega = (.89 - 2*alpha[i]*lambda)*k_inv[i],
diff2 = pos[i] - omega,
diff1 = vel[i] - lambda + alpha[i]*diff2;
pos[i] = osc_sin[i]*diff1 + osc_cos[i]*diff2 + lambda*.008 + omega;
vel[i] = dosc1[i]*diff1 - dosc2[i]*diff2 + lambda;
}
}
};
int main(int argc, char** argv){
Test t(SIZE);
t.compute();
std::cout << t.pos[10] << std::endl;
std::cout << t.vel[10] << std::endl;
}
以下是我的编译方式:
g++ -o test test.cpp -O3 -march=native -ffast-math -fopt-info-optimized
当DYNAMIC
设置为0
时,输出:
test.cpp:46:4: note: loop vectorized
但是当它设置为1
时它什么都不输出。
编译器没有对循环进行矢量化,因为它无法确定动态分配的指针不会相互混淆。允许对示例代码进行矢量化的一种简单方法是传递 --param vect-max-version-for-alias-checks=1000
选项。这将允许编译器发出所有必要的检查,以查看指针是否实际上是别名。
让您的示例代码矢量化的另一个简单解决方案是重命名 main
,正如 Marc Glisse 在他的评论中所建议的那样。名为 main
的函数显然禁用了某些优化。换个名字,GCC 4.9.2 可以跟踪 this->foo
(和其他指针成员)在 compute
中的使用,回到它们在 Test()
中的分配。
但是,我假设在名为 main
的函数中使用的 class 以外的东西阻止了您的代码在您的真实代码中被矢量化。允许您的代码在没有别名或对齐检查的情况下进行矢量化的更通用的解决方案是使用 restrict
关键字和 aligned
属性。像这样:
typedef float __attribute__((aligned(16))) float_a16;
__attribute__((noinline))
static void _compute(float_a16 * __restrict__ pos,
float_a16 * __restrict__ vel,
float_a16 * __restrict__ alpha,
float_a16 * __restrict__ k_inv,
float_a16 * __restrict__ osc_sin,
float_a16 * __restrict__ osc_cos,
float_a16 * __restrict__ dosc1,
float_a16 * __restrict__ dosc2,
int size) {
for (int i=0; i<size; i++){
float lambda = .67891*k_inv[i],
omega = (.89 - 2*alpha[i]*lambda)*k_inv[i],
diff2 = pos[i] - omega,
diff1 = vel[i] - lambda + alpha[i]*diff2;
pos[i] = osc_sin[i]*diff1 + osc_cos[i]*diff2 + lambda*.008 + omega;
vel[i] = dosc1[i]*diff1 - dosc2[i]*diff2 + lambda;
}
}
void compute() {
_compute(pos, vel, alpha, k_inv, osc_sin, osc_cos, dosc1, dosc2,
size);
}
noinline
属性很关键,否则内联会导致指针失去其限制性和对齐性。编译器似乎在函数参数以外的上下文中忽略了 restrict
关键字。
我用静态数组写了一些代码,它矢量化得很好。
float data[1024] __attribute__((aligned(16)));
我想动态分配数组。我试过这样做:
float *data = (float*) aligned_alloc(16, size*sizeof(float));
但是编译器 (GCC 4.9.2) 无法再对代码进行矢量化。我假设这是因为它不知道指针数据是 16 字节对齐的。我收到如下消息:
note: Unknown alignment for access: *_43
我试过在使用数据之前添加这一行,但它似乎没有做任何事情:
data = (float*) __builtin_assume_aligned(data, 16);
使用不同的变量 restrict
没有帮助:
float* __restrict__ align_data = (float*) __builtin_assume_aligned(data,16);
示例:
#include <iostream>
#include <stdlib.h>
#include <math.h>
#define SIZE 1024
#define DYNAMIC 0
#define A16 __attribute__((aligned(16)))
#define DA16 (float*) aligned_alloc(16, size*sizeof(float))
class Test{
public:
int size;
#if DYNAMIC
float *pos;
float *vel;
float *alpha;
float *k_inv;
float *osc_sin;
float *osc_cos;
float *dosc1;
float *dosc2;
#else
float pos[SIZE] A16;
float vel[SIZE] A16;
float alpha[SIZE] A16;
float k_inv[SIZE] A16;
float osc_sin[SIZE] A16;
float osc_cos[SIZE] A16;
float dosc1[SIZE] A16;
float dosc2[SIZE] A16;
#endif
Test(int arr_size){
size = arr_size;
#if DYNAMIC
pos = DA16;
vel = DA16;
alpha = DA16;
k_inv = DA16;
osc_sin = DA16;
osc_cos = DA16;
dosc1 = DA16;
dosc2 = DA16;
#endif
}
void compute(){
for (int i=0; i<size; i++){
float lambda = .67891*k_inv[i],
omega = (.89 - 2*alpha[i]*lambda)*k_inv[i],
diff2 = pos[i] - omega,
diff1 = vel[i] - lambda + alpha[i]*diff2;
pos[i] = osc_sin[i]*diff1 + osc_cos[i]*diff2 + lambda*.008 + omega;
vel[i] = dosc1[i]*diff1 - dosc2[i]*diff2 + lambda;
}
}
};
int main(int argc, char** argv){
Test t(SIZE);
t.compute();
std::cout << t.pos[10] << std::endl;
std::cout << t.vel[10] << std::endl;
}
以下是我的编译方式:
g++ -o test test.cpp -O3 -march=native -ffast-math -fopt-info-optimized
当DYNAMIC
设置为0
时,输出:
test.cpp:46:4: note: loop vectorized
但是当它设置为1
时它什么都不输出。
编译器没有对循环进行矢量化,因为它无法确定动态分配的指针不会相互混淆。允许对示例代码进行矢量化的一种简单方法是传递 --param vect-max-version-for-alias-checks=1000
选项。这将允许编译器发出所有必要的检查,以查看指针是否实际上是别名。
让您的示例代码矢量化的另一个简单解决方案是重命名 main
,正如 Marc Glisse 在他的评论中所建议的那样。名为 main
的函数显然禁用了某些优化。换个名字,GCC 4.9.2 可以跟踪 this->foo
(和其他指针成员)在 compute
中的使用,回到它们在 Test()
中的分配。
但是,我假设在名为 main
的函数中使用的 class 以外的东西阻止了您的代码在您的真实代码中被矢量化。允许您的代码在没有别名或对齐检查的情况下进行矢量化的更通用的解决方案是使用 restrict
关键字和 aligned
属性。像这样:
typedef float __attribute__((aligned(16))) float_a16;
__attribute__((noinline))
static void _compute(float_a16 * __restrict__ pos,
float_a16 * __restrict__ vel,
float_a16 * __restrict__ alpha,
float_a16 * __restrict__ k_inv,
float_a16 * __restrict__ osc_sin,
float_a16 * __restrict__ osc_cos,
float_a16 * __restrict__ dosc1,
float_a16 * __restrict__ dosc2,
int size) {
for (int i=0; i<size; i++){
float lambda = .67891*k_inv[i],
omega = (.89 - 2*alpha[i]*lambda)*k_inv[i],
diff2 = pos[i] - omega,
diff1 = vel[i] - lambda + alpha[i]*diff2;
pos[i] = osc_sin[i]*diff1 + osc_cos[i]*diff2 + lambda*.008 + omega;
vel[i] = dosc1[i]*diff1 - dosc2[i]*diff2 + lambda;
}
}
void compute() {
_compute(pos, vel, alpha, k_inv, osc_sin, osc_cos, dosc1, dosc2,
size);
}
noinline
属性很关键,否则内联会导致指针失去其限制性和对齐性。编译器似乎在函数参数以外的上下文中忽略了 restrict
关键字。