GCC Aliasing Checks w/Ограничивающие указатели
Рассмотрим следующие два фрагмента:
#define ALIGN_BYTES 32
#define ASSUME_ALIGNED(x) x = __builtin_assume_aligned(x, ALIGN_BYTES)
void fn0(const float *restrict a0, const float *restrict a1,
float *restrict b, int n)
{
ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);
for (int i = 0; i < n; ++i)
b[i] = a0[i] + a1[i];
}
void fn1(const float *restrict *restrict a, float *restrict b, int n)
{
ASSUME_ALIGNED(a[0]); ASSUME_ALIGNED(a[1]); ASSUME_ALIGNED(b);
for (int i = 0; i < n; ++i)
b[i] = a[0][i] + a[1][i];
}
Когда я компилирую функцию как gcc-4.7.2 -Ofast -march=native -std=c99 -ftree-vectorizer-verbose=5 -S test.c -Wall
, я обнаруживаю, что GCC вставляет aliasing проверки для второй функции.
Как я могу предотвратить это, чтобы результирующая сборка для fn1
была такой же, как для fn0
? (Когда число параметров увеличивается от трех до, скажем, 30, подход с аргументацией (fn0
) становится громоздким, а число проверок сглаживания в подходе fn1
становится смешным.)
Сборка (x86-64, чип, совместимый с AVX); aliasing cruft at.LFB10
fn0:
.LFB9:
.cfi_startproc
testl %ecx, %ecx
jle .L1
movl %ecx, %r10d
shrl $3, %r10d
leal 0(,%r10,8), %r9d
testl %r9d, %r9d
je .L8
cmpl $7, %ecx
jbe .L8
xorl %eax, %eax
xorl %r8d, %r8d
.p2align 4,,10
.p2align 3
.L4:
vmovaps (%rsi,%rax), %ymm0
addl $1, %r8d
vaddps (%rdi,%rax), %ymm0, %ymm0
vmovaps %ymm0, (%rdx,%rax)
addq $32, %rax
cmpl %r8d, %r10d
ja .L4
cmpl %r9d, %ecx
je .L1
.L3:
movslq %r9d, %rax
salq $2, %rax
addq %rax, %rdi
addq %rax, %rsi
addq %rax, %rdx
xorl %eax, %eax
.p2align 4,,10
.p2align 3
.L6:
vmovss (%rsi,%rax,4), %xmm0
vaddss (%rdi,%rax,4), %xmm0, %xmm0
vmovss %xmm0, (%rdx,%rax,4)
addq $1, %rax
leal (%r9,%rax), %r8d
cmpl %r8d, %ecx
jg .L6
.L1:
vzeroupper
ret
.L8:
xorl %r9d, %r9d
jmp .L3
.cfi_endproc
.LFE9:
.size fn0, .-fn0
.p2align 4,,15
.globl fn1
.type fn1, @function
fn1:
.LFB10:
.cfi_startproc
testq %rdx, %rdx
movq (%rdi), %r8
movq 8(%rdi), %r9
je .L12
leaq 32(%rsi), %rdi
movq %rdx, %r10
leaq 32(%r8), %r11
shrq $3, %r10
cmpq %rdi, %r8
leaq 0(,%r10,8), %rax
setae %cl
cmpq %r11, %rsi
setae %r11b
orl %r11d, %ecx
cmpq %rdi, %r9
leaq 32(%r9), %r11
setae %dil
cmpq %r11, %rsi
setae %r11b
orl %r11d, %edi
andl %edi, %ecx
cmpq $7, %rdx
seta %dil
testb %dil, %cl
je .L19
testq %rax, %rax
je .L19
xorl %ecx, %ecx
xorl %edi, %edi
.p2align 4,,10
.p2align 3
.L15:
vmovaps (%r9,%rcx), %ymm0
addq $1, %rdi
vaddps (%r8,%rcx), %ymm0, %ymm0
vmovaps %ymm0, (%rsi,%rcx)
addq $32, %rcx
cmpq %rdi, %r10
ja .L15
cmpq %rax, %rdx
je .L12
.p2align 4,,10
.p2align 3
.L20:
vmovss (%r9,%rax,4), %xmm0
vaddss (%r8,%rax,4), %xmm0, %xmm0
vmovss %xmm0, (%rsi,%rax,4)
addq $1, %rax
cmpq %rax, %rdx
ja .L20
.L12:
vzeroupper
ret
.L19:
xorl %eax, %eax
jmp .L20
.cfi_endproc
Ответы
Ответ 1
Отключить, чтобы сообщить компилятору прекратить проверку псевдонимов:
добавьте строку:
#pragma GCC ivdep
прямо перед циклом, который вы хотите векторизовать, если вам нужна дополнительная информация, пожалуйста, прочитайте:
https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Loop-Specific-Pragmas.html
Ответ 2
Может ли это помочь?
void fn1(const float **restrict a, float *restrict b, int n)
{
const float * restrict a0 = a[0];
const float * restrict a1 = a[1];
ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);
for (int i = 0; i < n; ++i)
b[i] = a0[i] + a1[i];
}
Изменить: вторая попытка:). С информацией из http://locklessinc.com/articles/vectorize/
gcc --fast-math ...
Ответ 3
Ну, а как насчет флага
-fno-strict-aliasing
?
Как я понял, вы правы, вы просто хотите знать, как отключить эти проверки? Если это все, этот параметр в командной строке gcc должен помочь вам.
EDIT:
В дополнение к вашему комментарию: запрещено ли использовать указатели ограничения типа const?
это из ISO/IEC 9899 (6.7.3.1 Формальное определение ограничения):
1.
Пусть D - объявление обычного идентификатора, которое предоставляет средство для обозначения объект P как указатель с ограничениями типа T.
4.
Во время каждого исполнения B пусть L - любое l-значение, которое имеет & L на основе P. Если L используется для доступ к значению объекта X, который он обозначает, и X также модифицируется (любым способом), то применяются следующие требования: T не должен быть const-квалифицированным. Все остальные значения используемый для доступа к значению X, также должен иметь свой адрес на основе P. Каждый доступ, который изменяет X, также следует рассматривать P для целей настоящего подпункта. Если P присваивается значение выражения указателя E, которое основано на другом ограниченном указателе объект P2, связанный с блоком B2, то либо выполнение B2 должно начинаться раньше выполнение B или выполнение B2 заканчивается до назначения. Если эти требования не выполняются, тогда поведение undefined.
И гораздо интереснее, так же как и в регистре:
6.
Переводчик может игнорировать любые или все последствия использования ограничений ограничения.
Итак, если вы не можете найти параметр команды, который заставляет gcc делать это, возможно, это невозможно, потому что по стандарту он не должен давать возможность сделать это.
Ответ 4
Извиняюсь заранее, потому что я не могу воспроизвести результаты с GCC 4.7 на своей машине, но есть два возможных решения.
-
Используйте typedef для правильной компоновки * restrict * restrict
. Это,
по словам бывшего коллеги, который разработчик компилятора LLVM,
единственное исключение из typedef
ведет себя как препроцессор в
C, и он существует, чтобы позволить вам поведение сглаживания.
Я попытался это сделать ниже, но я не уверен, что мне это удалось. Пожалуйста, внимательно проверьте мою попытку.
-
Используйте синтаксис, описанный в ответах на используя ограничитель ограничений с массивами переменной длины (VLA) C99.
Я попытался это сделать ниже, но я не уверен, что мне это удалось. Пожалуйста, внимательно проверьте мою попытку.
Вот код, который я использовал для выполнения своих экспериментов, но я не смог определить окончательно, если бы какой-либо из моих предложений работал по желанию.
#define ALIGN_BYTES 32
#define ASSUME_ALIGNED(x) x = __builtin_assume_aligned(x, ALIGN_BYTES)
void fn0(const float *restrict a0, const float *restrict a1,
float *restrict b, int n)
{
ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);
for (int i = 0; i < n; ++i)
b[i] = a0[i] + a1[i];
}
#if defined(ARRAY_RESTRICT)
void fn1(const float *restrict a[restrict], float * restrict b, int n)
#elif defined(TYPEDEF_SOLUTION)
typedef float * restrict frp;
void fn1(const frp *restrict a, float *restrict b, int n)
#else
void fn1(const float *restrict *restrict a, float *restrict b, int n)
#endif
{
//ASSUME_ALIGNED(a[0]); ASSUME_ALIGNED(a[1]); ASSUME_ALIGNED(b);
for (int i = 0; i < n; ++i)
b[i] = a[0][i] + a[1][i];
}
Опять же, я прошу прощения за наполовину испеченный характер этого ответа. Пожалуйста, не проголосуйте за меня, но не преуспевайте.