82 votes

Pourquoi l'ajout de commentaires d'assembly entraîne-t-il un changement aussi radical du code généré?

Donc, j'ai eu ce code:

constexpr unsigned N = 1000;
void f1(char* sum, char* a, char* b) {
    for(int i = 0; i < N; ++i) {
        sum[i] = a[i] + b[i];
    }
}

void f2(char* sum, char* a, char* b) {
    char* end = sum + N;
    while(sum != end) {
        *sum++ = *a++ + *b++;
    }
}

Je voulais voir le code de GCC 4.7.2 est susceptible de générer. Alors, j'ai couru g++ -march=native -O3 -masm=intel -S a.c++ -std=c++11 Et a obtenu le résultat suivant:

        .file   "a.c++"
        .intel_syntax noprefix
        .text
        .p2align 4,,15
        .globl  _Z2f1PcS_S_
        .type   _Z2f1PcS_S_, @function
_Z2f1PcS_S_:
.LFB0:
        .cfi_startproc
        lea     rcx, [rdx+16]
        lea     rax, [rdi+16]
        cmp     rdi, rcx
        setae   r8b
        cmp     rdx, rax
        setae   cl
        or      cl, r8b
        je      .L5
        lea     rcx, [rsi+16]
        cmp     rdi, rcx
        setae   cl
        cmp     rsi, rax
        setae   al
        or      cl, al
        je      .L5
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L3:
        movdqu  xmm0, XMMWORD PTR [rdx+rax]
        movdqu  xmm1, XMMWORD PTR [rsi+rax]
        paddb   xmm0, xmm1
        movdqu  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 992
        jne     .L3
        mov     ax, 8
        mov     r9d, 992
.L2:
        sub     eax, 1
        lea     rcx, [rdx+r9]
        add     rdi, r9
        lea     r8, [rax+1]
        add     rsi, r9
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L4:
        movzx   edx, BYTE PTR [rcx+rax]
        add     dl, BYTE PTR [rsi+rax]
        mov     BYTE PTR [rdi+rax], dl
        add     rax, 1
        cmp     rax, r8
        jne     .L4
        rep
        ret
.L5:
        mov     eax, 1000
        xor     r9d, r9d
        jmp     .L2
        .cfi_endproc
.LFE0:
        .size   _Z2f1PcS_S_, .-_Z2f1PcS_S_
        .p2align 4,,15
        .globl  _Z2f2PcS_S_
        .type   _Z2f2PcS_S_, @function
_Z2f2PcS_S_:
.LFB1:
        .cfi_startproc
        lea     rcx, [rdx+16]
        lea     rax, [rdi+16]
        cmp     rdi, rcx
        setae   r8b
        cmp     rdx, rax
        setae   cl
        or      cl, r8b
        je      .L19
        lea     rcx, [rsi+16]
        cmp     rdi, rcx
        setae   cl
        cmp     rsi, rax
        setae   al
        or      cl, al
        je      .L19
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L17:
        movdqu  xmm0, XMMWORD PTR [rdx+rax]
        movdqu  xmm1, XMMWORD PTR [rsi+rax]
        paddb   xmm0, xmm1
        movdqu  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 992
        jne     .L17
        add     rdi, 992
        add     rsi, 992
        add     rdx, 992
        mov     r8d, 8
.L16:
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L18:
        movzx   ecx, BYTE PTR [rdx+rax]
        add     cl, BYTE PTR [rsi+rax]
        mov     BYTE PTR [rdi+rax], cl
        add     rax, 1
        cmp     rax, r8
        jne     .L18
        rep
        ret
.L19:
        mov     r8d, 1000
        jmp     .L16
        .cfi_endproc
.LFE1:
        .size   _Z2f2PcS_S_, .-_Z2f2PcS_S_
        .ident  "GCC: (GNU) 4.7.2"
        .section        .note.GNU-stack,"",@progbits

Je suce à la lecture de l'assemblée, j'ai donc décidé d'ajouter quelques repères pour savoir où sont les corps de la boucle est allé:

constexpr unsigned N = 1000;
void f1(char* sum, char* a, char* b) {
    for(int i = 0; i < N; ++i) {
        asm("# im in ur loop");
        sum[i] = a[i] + b[i];
    }
}

void f2(char* sum, char* a, char* b) {
    char* end = sum + N;
    while(sum != end) {
        asm("# im in ur loop");
        *sum++ = *a++ + *b++;
    }
}

Et GCC cracha:

    .file   "a.c++"
    .intel_syntax noprefix
    .text
    .p2align 4,,15
    .globl  _Z2f1PcS_S_
    .type   _Z2f1PcS_S_, @function
_Z2f1PcS_S_:
.LFB0:
    .cfi_startproc
    xor eax, eax
    .p2align 4,,10
    .p2align 3
.L2:
#APP
# 4 "a.c++" 1
    # im in ur loop
# 0 "" 2
#NO_APP
    movzx   ecx, BYTE PTR [rdx+rax]
    add cl, BYTE PTR [rsi+rax]
    mov BYTE PTR [rdi+rax], cl
    add rax, 1
    cmp rax, 1000
    jne .L2
    rep
    ret
    .cfi_endproc
.LFE0:
    .size   _Z2f1PcS_S_, .-_Z2f1PcS_S_
    .p2align 4,,15
    .globl  _Z2f2PcS_S_
    .type   _Z2f2PcS_S_, @function
_Z2f2PcS_S_:
.LFB1:
    .cfi_startproc
    xor eax, eax
    .p2align 4,,10
    .p2align 3
.L6:
#APP
# 12 "a.c++" 1
    # im in ur loop
# 0 "" 2
#NO_APP
    movzx   ecx, BYTE PTR [rdx+rax]
    add cl, BYTE PTR [rsi+rax]
    mov BYTE PTR [rdi+rax], cl
    add rax, 1
    cmp rax, 1000
    jne .L6
    rep
    ret
    .cfi_endproc
.LFE1:
    .size   _Z2f2PcS_S_, .-_Z2f2PcS_S_
    .ident  "GCC: (GNU) 4.7.2"
    .section    .note.GNU-stack,"",@progbits

C'est beaucoup plus courte, et a des différences importantes, comme l'absence d'instructions SIMD. Je m'attendais à la même sortie, avec quelques commentaires, quelque part dans le milieu. Ai-je fait une mauvaise hypothèse ici? Est GCC optimiseur du entravée par l'asm commentaires?

62voto

Matthew Slattery Points 21628

Les interactions avec les optimisations sont expliquées à mi-chemin en bas de la "les Instructions de l'Assembleur, le C Opérandes de l'Expression" à la page dans la documentation.

GCC n'essayez pas de comprendre le montage à l'intérieur de l' asm; la seule chose qu'il sait à propos du contenu est ce que vous (en option) la raconter à la sortie et d'entrée d'opérande spécification et le registre clobber de la liste.

En particulier, la note:

Un asm instruction, sans aucune sortie opérandes soient traités de façon identique à un volatile asm enseignement.

et

L' volatile mot-clé indique que l'instruction a d'importants effets secondaires [...]

Si la présence de l' asm à l'intérieur de votre boucle a inhibé une vectorisation de l'optimisation, car GCC suppose qu'il a des effets secondaires.

23voto

Jester Points 15625

Notez que gcc vectorisé le code, le fractionnement du corps de la boucle en deux parties, la première de traitement de 16 points à la fois, et la seconde de faire le reste plus tard.

Comme Ira, a commenté, le compilateur ne pas analyser l'asm bloc, il ne sait pas que c'est juste un commentaire. Même si elle le faisait, elle n'a aucun moyen de savoir ce que vous souhaitiez. Le optmized boucles ont le corps doublé, devrait-il mettre votre asm dans chaque? Aimeriez-vous qu'il n'est pas exécuté 1000 fois? Il ne sait pas, il en va de la sécurité de l'itinéraire et revient à la simple boucle unique.

3voto

Mats Petersson Points 70074

Je ne suis pas d'accord avec le "gcc ne pas comprendre ce qui est dans l' asm() bloc". Par exemple, gcc peut faire face très bien avec l'optimisation de paramètres, et même re-arranger asm() blocs de telle sorte qu'il s'accorde avec le code C généré. C'est pourquoi, si vous regardez assembleur en ligne, par exemple le noyau Linux, il est presque toujours avec le préfixe __volatile__ pour s'assurer que le compilateur "ne pas déplacer le code autour de". J'ai eu gcc déplacer mon "rdtsc" autour de, qui fait à mes mesures du temps qu'il a fallu pour faire certaine chose.

Comme indiqué, gcc traite certains types d' asm() blocs "spécial", et n'a donc pas d'optimiser le code de chaque côté du bloc.

Cela ne veut pas dire que gcc n'est pas, parfois, de se confondre en assembleur en ligne, les blocs, ou tout simplement décider de renoncer à certains d'optimisation, car il ne peut pas suivre les conséquences de l'assembleur, etc, etc. Plus important encore, il peut souvent être troublés par le manque de tabasser des balises, donc si vous avez quelques instruction cpuid qui modifie la valeur de EAX-EDX, mais vous avez écrit le code pour qu'il utilise uniquement des EAX, le compilateur peut stocker des choses dans EBX, ECX et EDX, puis votre code d'actes très étrange quand ces registres sont écrasés... Si vous avez de la chance, il se bloque immédiatement alors il est facile de comprendre ce qui se passe. Mais si vous êtes malchanceux, il se bloque en bas de la ligne... une Autre question délicate est la fracture de l'instruction que de donner une seconde conséquence dans edx. Si vous n'avez pas de soins sur le modulo, c'est facile d'oublier que EDX a été changé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X