Скажите С++, что данные указателя выравниваются по 16 байт

Я написал некоторый код со статическими массивами, и он векторизует просто отлично.

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, он ничего не выводит.

Ответы

Ответ 1

Компилятор не векторизует цикл, потому что он не может определить, что динамически выделенные указатели не псевдонимы друг друга. Простым способом, чтобы ваш код кода был векторизован, должен передать параметр --param vect-max-version-for-alias-checks=1000. Это позволит компилятору исправить все проверки, необходимые, чтобы увидеть, действительно ли указатели наложены.

Еще одно простое решение, позволяющее использовать ваш примерный код для векторизации, - это переименовать main, как предложил Марк Глисс в своем комментарии. Функции с именем main, по-видимому, отключены. Именованный что-то еще, GCC 4.9.2 может отслеживать использование this->foo (и других элементов указателя) в compute обратно к их выделениям в Test().

Однако я предполагаю, что что-то другое, кроме вашего класса, которое используется в функции с именем main, помешало вашему вектору векторизовать ваш реальный код. Более общее решение, которое позволяет векторизовать ваш код без проверки наложения или выравнивания, заключается в использовании ключевого слова 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 в контексте, отличном от параметров функции.