Вот несколько примеров кода C, который делает то, что вы хотите
#include <stdio.h>
#include <x86intrin.h>
#include <inttypes.h>
#define ALIGN 32
#define SIMD_WIDTH (ALIGN/sizeof(double))
int main(void) {
int n = 17;
int c = 1;
double* p = _mm_malloc((n+c) * sizeof *p, ALIGN);
double* p1 = p+c;
for(int i=0; i<n; i++) p1[i] = 1.0*i;
double* p2 = (double*)((uintptr_t)(p1+SIMD_WIDTH-1)&-ALIGN);
double* p3 = (double*)((uintptr_t)(p1+n)&-ALIGN);
if(p2>p3) p2 = p3;
printf("%p %p %p %p\n", p1, p2, p3, p1+n);
double *t;
for(t=p1; t<p2; t+=1) {
printf("a %p %f\n", t, *t);
}
puts("");
for(;t<p3; t+=SIMD_WIDTH) {
printf("b %p ", t);
for(int i=0; i<SIMD_WIDTH; i++) printf("%f ", *(t+i));
puts("");
}
puts("");
for(;t<p1+n; t+=1) {
printf("c %p %f\n", t, *t);
}
}
Это создает 32-байтовый выровненный буфер а а затем сдвигается на один двойной в размере, так что это больше не 32-байтовый выровнены. Он перескакивает через скалярные значения до достижения 32-битного выравнивания, обходит по 32-байтовым выровненным значениям и затем заканчивает другим скалярным циклом для любых оставшихся значений, которые не являются кратными ширине SIMD.
Я бы сказал, что такая оптимизация действительно имеет большое значение для процессоров Intel x86 до Nehalem. Так как Nehalem латентность и пропускная способность неуравновешенных нагрузок и хранилищ такие же, как для выровненных нагрузок и хранилищ. Кроме того, поскольку Nehalem затраты на разделение строк кэша малы.
Существует одна тонкая точка с SSE, так как Nehalem в том, что неуравновешенные нагрузки и магазины не могут складываться с другими операциями. Поэтому выровненные нагрузки и хранилища не устаревают с SSE с Nehalem. Поэтому в принципе эта оптимизация может иметь значение даже с Nehalem, но на практике я думаю, что в этом случае мало случаев.
Тем не менее, при несогласованных нагрузках и хранилищах AVX могут складываться, поэтому выровненные нагрузки и инструкции хранения устарели.
I looked into this with GCC, MSVC, and Clang. GCC, если он не может принять указатель, выравнивается, например, 16 байтов с SSE, тогда он будет генерировать код, аналогичный приведенному выше коду, чтобы достичь выравнивания по 16 байт, чтобы избежать расщепления строки кэша при векторизации.
Clang и MSVC не делают этого, чтобы пострадали от расщепления линии кэширования. Тем не менее, стоимость дополнительного кода для этого компенсирует затраты на разделение кэша, что, вероятно, объясняет, почему Clang и MSVC не беспокоятся об этом.
Исключение составляет только Nahalem. В этом случае GCC намного быстрее, чем Clang и MSVC, когда указатель не выровнен. Если указатель выровнен и Clang знает его, он будет использовать выровненные нагрузки и магазины и быть быстрым, как GCC.В векторизации MSVC по-прежнему используются неустановленные хранилища и нагрузки и, следовательно, медленный pre-Nahalem, даже если указатель выравнивается по 16 байт.
Вот вариант, который я думаю, что это немного яснее, используя различия указателей
#include <stdio.h>
#include <x86intrin.h>
#include <inttypes.h>
#define ALIGN 32
#define SIMD_WIDTH (ALIGN/sizeof(double))
int main(void) {
int n = 17, c =1;
double* p = _mm_malloc((n+c) * sizeof *p, ALIGN);
double* p1 = p+c;
for(int i=0; i<n; i++) p1[i] = 1.0*i;
double* p2 = (double*)((uintptr_t)(p1+SIMD_WIDTH-1)&-ALIGN);
double* p3 = (double*)((uintptr_t)(p1+n)&-ALIGN);
int n1 = p2-p1, n2 = p3-p2;
if(n1>n2) n1=n2;
printf("%d %d %d\n", n1, n2, n);
int i;
for(i=0; i<n1; i++) {
printf("a %p %f\n", &p1[i], p1[i]);
}
puts("");
for(;i<n2; i+=SIMD_WIDTH) {
printf("b %p ", &p1[i]);
for(int j=0; j<SIMD_WIDTH; j++) printf("%f ", p1[i+j]);
puts("");
}
puts("");
for(;i<n; i++) {
printf("c %p %f\n", &p1[i], p1[i]);
}
}
Вы смотрели на код авто-векторизации компилятор, как '' gcc' или clang' будет производить дело с возможной несоосностью? Я подозреваю, что вы не совсем знаете, в чем вы попадаете. – EOF
Да, разложение массива на указатель и выполнение арифметики на нем поможет вам определить, находится ли он на хорошем выровненном адресе или нет. * Но ... * Проверка этого не поможет. Если адрес первого элемента не будет хорошо выровнен, адреса в * все * элементы могут быть неровными или, по крайней мере, многими или большинством элементов. Лучшей идеей может быть использование динамического распределения и добавление начального заполнения, чтобы убедиться, что у вас выровненный «массив». –
@JoachimPileborg: Я думал, что речь идет о смещении данных для векторных команд, и в этом случае отдельные * элементы * вектора * выравниваются *. – EOF