2015-05-27 8 views
9

Esiste un prcma di gcc o qualcosa che posso usare per forzare gcc a generare istruzioni senza branch su una specifica sezione di codice?Fai gcc usare le mosse condizionali

Ho un pezzo di codice che voglio gcc per compilare a codice libero-ramo utilizzando le istruzioni cmov:

int foo(int *a, int n, int x) { 
    int i = 0, j = n; 

    while (i < n) { 
#ifdef PREFETCH 
     __builtin_prefetch(a+16*i + 15); 
#endif /* PREFETCH */ 
     j = (x <= a[i]) ? i : j; 
     i = (x <= a[i]) ? 2*i + 1 : 2*i + 2; 
    } 
    return j; 
} 

e, anzi, lo fa:

[email protected]$ gcc -O4 -S -c test.c -o -  
    .file "test.c" 
    .text 
    .p2align 4,,15 
    .globl foo 
    .type foo, @function 
foo: 
.LFB0: 
    .cfi_startproc 
    testl %esi, %esi 
    movl %esi, %eax 
    jle .L2 
    xorl %r8d, %r8d 
    jmp .L3 
    .p2align 4,,10 
    .p2align 3 
.L6: 
    movl %ecx, %r8d 
.L3: 
    movslq %r8d, %rcx 
    movl (%rdi,%rcx,4), %r9d 
    leal (%r8,%r8), %ecx  # put 2*i in ecx 
    leal 1(%rcx), %r10d  # put 2*i+1 in r10d 
    addl $2, %ecx    # put 2*i+2 in ecx 
    cmpl %edx, %r9d 
    cmovge %r10d, %ecx   # put 2*i+1 in ecx if appropriate 
    cmovge %r8d, %eax   # set j = i if appropriate 
    cmpl %esi, %ecx 
    jl .L6 
.L2: 
    rep ret 
    .cfi_endproc 
.LFE0: 
    .size foo, .-foo 
    .ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" 
    .section .note.GNU-stack,"",@progbits 

(Sì, Mi rendo conto che il ciclo è un ramo, ma sto parlando degli operatori di scelta all'interno del ciclo.)

Sfortunatamente, quando abilito la chiamata __builtin_prefetch, gcc genera il codice ramificato:

[email protected]$ gcc -DPREFETCH -O4 -S -c test.c -o - 
    .file "test.c" 
    .text 
    .p2align 4,,15 
    .globl foo 
    .type foo, @function 
foo: 
.LFB0: 
    .cfi_startproc 
    testl %esi, %esi 
    movl %esi, %eax 
    jle .L7 
    xorl %ecx, %ecx 
    jmp .L5 
    .p2align 4,,10 
    .p2align 3 
.L3: 
    movl %ecx, %eax   # this is the x <= a[i] branch 
    leal 1(%rcx,%rcx), %ecx 
    cmpl %esi, %ecx 
    jge .L11 
.L5: 
    movl %ecx, %r8d   # this is the main branch 
    sall $4, %r8d    # setup the prefetch 
    movslq %r8d, %r8   # setup the prefetch 
    prefetcht0 60(%rdi,%r8,4) # do the prefetch 
    movslq %ecx, %r8 
    cmpl %edx, (%rdi,%r8,4) # compare x with a[i] 
    jge .L3 
    leal 2(%rcx,%rcx), %ecx # this is the x > a[i] branch 
    cmpl %esi, %ecx 
    jl .L5 
.L11: 
    rep ret 
.L7: 
    .p2align 4,,5 
    rep ret 
    .cfi_endproc 
.LFE0: 
    .size foo, .-foo 
    .ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" 
    .section .note.GNU-stack,"",@progbits 

Ho provato a utilizzare __attribute__((optimize("if-conversion2"))) su questa funzione, ma ciò non ha alcun effetto.

Il motivo per cui mi preme molto è che ho elaborato codice senza branch generato dal compilatore modificato a mano (dal primo esempio) per includere le istruzioni prefetcht0 e viene eseguito molto più velocemente di entrambe le versioni prodotte da gcc.

+1

livello ciò che l'ottimizzazione stai compila con? perché ho davvero difficoltà a battere il compilatore quando scrivo codice normale e uso -O3 o -Ofast –

+0

Immagino che tu possa suggerire gcc sulla probabilità che la condizione sia vera o falsa. Sembra che il tuo gcc sia molto convinto che il ramo sia per lo più vero o per lo più falso in modo che la previsione del tempo di esecuzione funzioni meglio della previsione? – user3528438

+0

@GradyPlayer: Questo è compilato con -O4, e nel primo esempio utilizza felicemente le operazioni di cmov. –

risposta

5

Se si fa realmente affidamento su quel livello di ottimizzazione, è necessario scrivere i propri matrici assembler.

La ragione è che anche una modifica altrove nel codice potrebbe modificare il codice emesso dal compilatore (che non è specifico per gcc). Inoltre, una versione diversa di gcc, diverse opzioni (ad es. -fomit-frame-pointer) possono cambiare drasticamente il codice.

Si dovrebbe davvero fare solo questo se necessario. Altre influenze potrebbero avere un impatto molto maggiore, come la configurazione della cache, l'allocazione della memoria (pagina DRAM/banco), l'ordine di esecuzione rispetto ai programmi eseguiti contemporaneamente, l'associazione della CPU e molto altro. Gioca prima con le ottimizzazioni del compilatore. Opzioni della riga di comando che trovi nello docs (non hai pubblicato la versione utilizzata, quindi non più specifica).

Un'alternativa (seria) sarebbe quella di utilizzare clang/llvm. O semplicemente aiuta il team di gcc a migliorare i loro ottimizzatori. Non saresti il ​​primo. Nota anche che gcc ha apportato enormi miglioramenti specifici per ARM nelle ultime versioni.

+0

Sfortunatamente, questo non è accettabile. Il codice sopra riportato è solo un esempio di lavoro minimo. In realtà, questo è un metodo C++ basato su modelli in cui "int * a" è "T * a" e "int x" è "T x". Non riesco a scrivere gli stub di assemblaggio per ogni tipo possibile T. Questo codice fa parte di un progetto per studiare gli effetti dei layout di matrice sulla ricerca. L'istruzione prefetch è molto specifica per un motivo: la riga della cache contenente l'indirizzo prefetched sarà accessibile esattamente quattro iterazioni dopo l'emissione dell'istruzione prefetch. –

+0

@PatMorin: nessuna ragione per incolpare (downvote? - cattivo atteggiamento) il messaggero per un messaggio sgradevole. Molto probabilmente il team di gcc sarà felice se li aiuti a migliorare i loro algoritmi di ottimizzazione. In alternativa, potresti provare clang/llvm; potrebbe rivelarsi migliore qui, o - almeno - dovrebbe essere più facile da estendere con il proprio ottimizzatore. Nota inoltre che hai taggato la domanda C e non ci sono modelli in C, quindi potresti anche incolparti per aver fornito informazioni sbagliate. – Olaf

+0

Abbastanza giusto. Scusate. Volevo solo sottolineare che scrivere assemblea non è una risposta alla domanda e ha fatto saltare la pistola. Rimuoverò il mio downvote, ma a quanto pare non sono autorizzato a meno che tu non cambi la tua risposta. –

4

Sembra che gcc potrebbe avere problemi a generare codice senza branch sulle variabili utilizzate in condizioni di loop e post-condizioni, insieme con i vincoli di mantenere i registri temporanei vivi attraverso una chiamata intrinseca pseudo-funzione.

C'è qualcosa di sospetto, il codice generato dalla tua funzione è diverso quando si utilizza -funroll-all-loops e -fguess-branch-probability. Io genera molte istruzioni di ritorno. Ha un odore simile a un piccolo bug in gcc, attorno al passaggio rtl del compilatore o alle semplificazioni dei blocchi di codici.

Il seguente codice è senza diramazione in entrambi i casi. Questa sarebbe una buona ragione per presentare un bug a GCC. A livello -O3, GCC dovrebbe sempre generare lo stesso codice.

int foo(int *a, int n, int x) { 
    int c, i = 0, j = n; 

    while (i < n) { 
#ifdef PREFETCH 
     __builtin_prefetch(a+16*i + 15); 
#endif /* PREFETCH */ 
     c = (x > a[i]); 
     j = c ? j : i; 
     i = 2*i + 1 + c; 
    } 
    return j; 
} 

che genera questo

 .cfi_startproc 
     testl %esi, %esi 
     movl %esi, %eax 
     jle  .L4 
     xorl %ecx, %ecx 
     .p2align 4,,10 
     .p2align 3 
.L3: 
     movslq %ecx, %r8 
     cmpl %edx, (%rdi,%r8,4) 
     setl %r8b 
     cmovge %ecx, %eax 
     movzbl %r8b, %r8d 
     leal 1(%r8,%rcx,2), %ecx 
     cmpl %ecx, %esi 
     jg  .L3 
.L4: 
     rep ret 
     .cfi_endproc 

e questo

 .cfi_startproc 
     testl %esi, %esi 
     movl %esi, %eax 
     jle  .L5 
     xorl %ecx, %ecx 
     .p2align 4,,10 
     .p2align 3 
.L4: 
     movl %ecx, %r8d 
     sall $4, %r8d 
     movslq %r8d, %r8 
     prefetcht0  60(%rdi,%r8,4) 
     movslq %ecx, %r8 
     cmpl %edx, (%rdi,%r8,4) 
     setl %r8b 
     testb %r8b, %r8b 
     movzbl %r8b, %r9d 
     cmove %ecx, %eax 
     leal 1(%r9,%rcx,2), %ecx 
     cmpl %ecx, %esi 
     jg  .L4 
.L5: 
     rep ret 
     .cfi_endproc