GCC-Aliasing-Prüfungen mit eingeschränkten Zeigern

Lesezeit: 8 Minuten

Benutzeravatar von Freddie Witherden
Freddie Witherden

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

  • 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

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

  • Wir sind bei 4.7.2, nicht wahr? 4.7.4 gibt mir: Warnung: Ignoriere #pragma GCC ivdep [-Wunknown-pragmas]

    – Bodo Thiesen

    27. Juli 2015 um 11:11 Uhr


Benutzeravatar von Valeri Atamaniouk
Waleri Atamaniouk

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 ...

  • Leider nicht; GCC gibt immer noch Aliasing-Prüfungen aus.

    – Freddie Witherden

    25. März 2013 um 12:36 Uhr

Benutzeravatar von dhein
dhein

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.

  • @Freddie Witherden Ich bin nicht ziemlich gut in Assembler, aber ich weiß, dass die von Ihnen erwähnten Zeilen durch dieses Flag (das ich bisher in meinen Fällen getestet habe) aus dem.o heraus sein werden. und ich weiß auch, dass diese Zeile dem Compiler rät, keine Optimierung basierend auf Aliasing-Regeln vorzunehmen, also schlage ich vor, dass er mit diesem Flag kein Aliasing überprüft, wenn er nicht abhängig von diesen Überprüfungen handeln darf. Das sollte also Ihr Problem lösen, ja.

    – dhein

    13. August 2013 um 8:48 Uhr

  • @Freddie Witherden, nur um es richtig zu machen: Was Sie tun möchten, ist, den Compiler daran zu hindern, eine Optimierung basierend auf strikten Aliasings durchzuführen, nicht wahr? denn genau das ist der Zweck dieses Parameters. Also sollte es tun, was Sie wollen, wie ich Sie richtig verstanden habe.

    – dhein

    15. August 2013 um 12:17 Uhr

  • Nicht genau. Ich möchte, dass der Compiler das Einschränkungskennzeichen für die übergebenen Variablen respektiert. Obwohl Aliasing verwandt ist, unterscheidet es sich von GCCs striktem Aliasing.

    – Freddie Witherden

    26. August 2013 um 12:14 Uhr

  • @Freddie Witherden Aber ist es nicht verboten, nach 6.7.3.1 den Beschränkungszeiger vom Typ Const zu verwenden? Formale Definition von Beschränkung?

    – dhein

    26. August 2013 um 12:25 Uhr

  • s/einschränken/__einschränken__/; s/std=c99/std=gnu99/; Mein Interesse besteht hier nur darin, GCC dazu zu bringen, die bestmögliche Baugruppe zu produzieren.

    – Freddie Witherden

    26. August 2013 um 21:48 Uhr

Benutzeravatar von Jeff Hammond
Jeff Hammond

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.

  1. 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.

  2. 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.

1386530cookie-checkGCC-Aliasing-Prüfungen mit eingeschränkten Zeigern

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy