Betrachten Sie die folgenden zwei Ausschnitte:
#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];
}
Wenn ich die Funktion als kompiliere gcc-4.7.2 -Ofast -march=native -std=c99 -ftree-vectorizer-verbose=5 -S test.c -Wall
Ich finde, dass GCC Aliasing-Prüfungen für die zweite Funktion einfügt.
Wie kann ich dies verhindern, so dass die resultierende Baugruppe z fn1
ist das gleiche wie für fn0
? (Wenn die Anzahl der Parameter von drei auf beispielsweise 30 ansteigt, wird der Argumentübergabeansatz (fn0
) wird umständlich und die Anzahl der Aliasing-Prüfungen in der fn1
Ansatz wird lächerlich.)
Montage (x86-64, AVX-fähiger Chip); Aliasing cruft bei .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
Es gibt keine Möglichkeit, dem Compiler mitzuteilen, dass er die Überprüfung von Aliasing beenden soll:
Bitte Zeile hinzufügen:
#pragma GCC ivdep
direkt vor der Schleife, die Sie vektorisieren möchten, wenn Sie weitere Informationen benötigen, lesen Sie bitte:
https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Loop-Specific-Pragmas.html
Kann das helfen?
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];
}
Edit: Zweiter Versuch :). Mit Informationen von http://locklessinc.com/articles/vectorize/
gcc --fast-math ...
Nun, was ist mit der Flagge
-fno-strict-aliasing
?
Wie ich Sie richtig verstanden habe, möchten Sie nur wissen, wie Sie diese Überprüfungen deaktivieren können? Wenn das alles ist, sollte Ihnen dieser Parameter für die gcc-Befehlszeile helfen.
BEARBEITEN:
Zusätzlich zu Ihrem Kommentar: Ist es nicht verboten, Const-Type-Restrict-Zeiger zu verwenden?
dies ist aus ISO/IEC 9899 (6.7.3.1 Formale Definition von Beschränkung):
1.
Sei D eine Deklaration eines gewöhnlichen Bezeichners, der ein Mittel bereitstellt, um ein Objekt P als einen eingeschränkt qualifizierten Zeiger auf den Typ T zu bezeichnen.
4.
Während jeder Ausführung von B sei L ein beliebiger L-Wert, der &L auf der Grundlage von P hat. Wenn L verwendet wird, um auf den Wert des Objekts X zuzugreifen, das es bezeichnet, und X auch modifiziert wird (auf beliebige Weise), dann gelten die folgenden Anforderungen : T darf nicht const-qualifiziert sein. Jeder andere L-Wert, der verwendet wird, um auf den Wert von X zuzugreifen, muss seine Adresse ebenfalls auf P basieren haben. Jeder Zugriff, der X modifiziert, gilt für die Zwecke dieses Unterabschnitts auch als Modifizierung von P. Wenn P der Wert eines Zeigerausdrucks E zugewiesen wird, der auf einem anderen beschränkten Zeigerobjekt P2 basiert, das Block B2 zugeordnet ist, dann soll entweder die Ausführung von B2 vor der Ausführung von B beginnen oder die Ausführung von B2 vor der enden Abtretung. Wenn diese Anforderungen nicht erfüllt sind, ist das Verhalten undefiniert.
Und ein viel interessanterer Punkt, genau wie beim Register, ist dieser:
6.
Einem Übersetzer steht es frei, irgendwelche oder alle Aliasing-Implikationen der Verwendung von „restrict“ zu ignorieren.
Wenn Sie also keinen Befehlsparameter finden können, der gcc dazu zwingt, ist dies wahrscheinlich nicht möglich, da es standardmäßig keine Option dazu geben muss.
Ich entschuldige mich im Voraus, da ich mit GCC 4.7 auf meinem Rechner keine Ergebnisse reproduzieren kann, aber es gibt zwei mögliche Lösungen.
-
Verwenden Sie typedef, um a zu erstellen * restrict * restrict
richtig. Dies ist laut einem ehemaligen Kollegen, der den LLVM-Compiler entwickelt, die einzige Ausnahme typedef
verhält sich wie der Präprozessor in C und ermöglicht das gewünschte Anti-Aliasing-Verhalten.
Ich habe dies unten versucht, bin mir aber nicht sicher, ob es mir gelungen ist. Bitte überprüfen Sie meinen Versuch sorgfältig.
-
Verwenden Sie die in den Antworten zur Verwendung des Einschränkungsqualifizierers mit C99-Arrays mit variabler Länge (VLAs) beschriebene Syntax.
Ich habe dies unten versucht, bin mir aber nicht sicher, ob es mir gelungen ist. Bitte überprüfen Sie meinen Versuch sorgfältig.
Hier ist der Code, mit dem ich meine Experimente durchgeführt habe, aber ich konnte nicht abschließend feststellen, ob einer meiner Vorschläge wie gewünscht funktioniert hat.
#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];
}
Nochmals, ich entschuldige mich für die unausgegorene Natur dieser Antwort. Bitte stimmen Sie mich nicht dafür ab, dass ich es versucht habe, aber keinen Erfolg habe.
Hat die Option
--param vect-max-version-for-alias-checks=n
überhaupt helfen?– teppisch
25. März 2013 um 11:52 Uhr
Es hilft, wenn viele Zeiger im Spiel sind (oft gibt GCC einfach den Versuch auf, eine Funktion zu vektorisieren, es sei denn, n ~ 100). Ich frage mich jedoch, wie ich GCC davon überzeugen kann, dass diese Überprüfungen sinnlos sind.
– Freddie Witherden
25. März 2013 um 12:00 Uhr
Könnten Sie die Assembly zeigen, die Ihr Compiler generiert?
– Pascal Cuoq
25. März 2013 um 12:00 Uhr
Dieses Papier könnte hilfreich sein: cs.cmu.edu/~dkoes/research/techreport.pdf
– teppisch
25. März 2013 um 12:55 Uhr
@Freddie Witherden, also was ist jetzt mit -fno-strict-aliasing? Hat es dir geholfen?
– dhein
11. August 2013 um 13:17 Uhr