2010-09-13 20 views
36

Esiste un modo portatile per eseguire suggerimenti di previsione ramo? Si consideri il seguente esempio:Suggerimenti di previsione ramo portatile

if (unlikely_condition) { 
    /* ..A.. */ 
    } else { 
    /* ..B.. */ 
    } 

È questo diverso che farlo:

if (!unlikely_condition) { 
    /* ..B.. */ 
    } else { 
    /* ..A.. */ 
    } 

O è l'unico modo per utilizzare i parametri specifici del compilatore? (ad es. __builtin_expect su GCC)

I compilatori considereranno le condizioni di if in modo diverso in base all'ordine delle condizioni?

+0

Mi chiedo se questo potrebbe essere qualcosa C++ 0x attributi da applicare alle condizioni per 'if'? Mi piace 'if ([[improbabile]] unlikely_condition) {...}'? Attualmente la sintassi non lo consente. Tuttavia * consente * se ([[improbabile]] bool b = ...) {} '. Forse si potrebbe abusare di questo :) –

+4

Il codice GNU contiene una quantità ridicola di 'se (probabile (...))' junk nel codice completamente non performante, e IMO questo è davvero pessimo. Per prima cosa, non si legge naturalmente in inglese - suona come "se questa condizione è probabile che sia vera" invece di "se questa condizione è vera, che probabilmente è". E per un altro, è solo confusione. A meno che non siano presenti molti condizionali critici per le prestazioni che non verranno compilati con 'cmov' o simili, ignorare semplicemente l'hint di previsione dei rami. –

+0

@R .. Penso di capire perché il kernel di Linux è pieno di 'if (improbabile (...))'. Preferiscono le uscite anticipate che semplificano il flusso di codice da seguire. Se non lo facessero, la previsione del ramo statico fallirebbe sempre. –

risposta

25

Il modo più tradizionale per fare branch prediction statico è che if è previsto non-ramificato (cioè ogni clausola if viene eseguito, non else), e loop e backward- goto s sono prese. Quindi, non mettere il caso comune in else se si prevede che la previsione statica sia significativa. Andare in giro per un ciclo non prelevato non è così facile; Non ho mai provato, ma suppongo che metterlo in una clausola else dovrebbe funzionare abbastanza portabilmente.

Molti compilatori supportano alcune forme di #pragma unroll, ma sarà comunque necessario proteggerlo con una sorta di #if per proteggere altri compilatori.

I suggerimenti di predizione di ramo possono teoricamente esprimere una descrizione completa di come trasformare il grafico di controllo di flusso di un programma e disporre i blocchi di base nella memoria eseguibile ... quindi c'è una varietà di cose da esprimere e la maggior parte non sarà molto portabile .

Come consigliato da GNU nella documentazione per __builtin_expect, l'ottimizzazione guidata dal profilo è superiore ai suggerimenti e con uno sforzo minore.

+0

E VS ha anche PGO, quindi è una soluzione vincente. :) – GManNickG

1

Basta essere coerenti con quello che fai. Mi piace usare

if (!(someExpression)) 

Ma il compilatore dovrebbe trattare questo allo stesso modo.

7

L'ottimizzazione è intrinsecamente una cosa del compilatore, quindi è necessario utilizzare la funzionalità del compilatore per aiutarlo. Il linguaggio in sé non si preoccupa dell'ottimizzazione (o del mandato).

Quindi il meglio che puoi fare senza estensioni specifiche del compilatore è organizzare il tuo codice in modo tale che i tuoi compilatori "facciano la cosa giusta" senza aiuto. Ma se vuoi essere sicuro, tocca le estensioni del compilatore. (Si potrebbe provare astraendoli dietro il preprocessore, in modo che il codice rimane portatile.)

+4

C'è un sacco di precedenti per il linguaggio che fornisce suggerimenti per l'ottimizzazione, comunque ('inline',' restrict', 'register'). Alcuni sono più puntigliosi di altri sui compilatori moderni. Non importa se una particolare implementazione fa effettivamente qualcosa con loro: se c'è una ragionevole possibilità che qualcuno faccia qualcosa di utile, allora sarebbe una bella caratteristica. Suppongo che la predizione del ramo statico nel principale non soddisfi questo criterio, quindi non penso che sia una cattiva chiamata lasciarlo fuori. È un giudizio sul merito del caso, anche se non proprio "non ci interessa l'ottimizzazione, mai". –

+0

@Steve: Sì, immagino di ignorare automaticamente quelli per cui mi dimentico di loro. Hai ragione. – GManNickG

+2

Penso che 'restrict' sia probabilmente utile come ottimizzazione prematura. Non farà alcun danno, potrebbe fare un bene significativo prevenendo le orribili dipendenze da archivio/carico, e dove si applica è presumibilmente un requisito documentato (come in 'memcpy') se ciò si riflette nella fonte o meno. Gli altri (e in C++ 03), sono d'accordo con il tuo clamoroso "meh" :-) –

15

Nella maggior parte dei casi, il seguente codice

if (a) 
{ 
    ... 
} 
else 
{ 
    ... 
} 

è in realtà

evaluate(A) 

if (!A) 
{ 
    jmp p1 
} 

... code A 

    jmp p2 

p1: 

... code !A 

p2: 

Si noti che se A è vero, "codice A" è già in cantiere. Il processore vedrà il comando "jmp p2" in avanti e caricherà il codice p2 nella pipeline.

Se A è falso, il "codice! A" potrebbe non essere nella pipeline, quindi potrebbe essere più lento.

Conclusioni:

  1. fare se (X) se X è più probabile che X
  2. cercare di valutare AL più presto possibile, in modo che la CPU può dynmically di ottimizzare la pipeline!.

:

evaluate(A) 

do more stuff 

if (A) 
    ... 
+0

capito! Si prega di mantenere il codice 'if' in parentesi graffe, anche se la sua singola riga. Altrimenti un po 'di confusione! – Ayyappa

+0

"prova a valutare A il prima possibile, in modo che la CPU possa ottimizzare dinamicamente la pipeline." - Puoi fornire alcuni suggerimenti per questa affermazione? Voglio sapere come lo farà. – Ayyappa

+0

Un sacco di informazioni qui: http://download.intel.com/design/pentiumii/manuals/24281603.pdf –

1

Cosa c'è di sbagliato con il controllo di un compilatore specifico tramite #ifdef e hai nascosto queste cose dietro una macro personalizzata? Puoi #define espandersi all'espressione semplice nei casi in cui non hai un compilatore che supporti questi suggerimenti di ottimizzazione. Recentemente ho fatto qualcosa di simile con prefetches cache espliciti che GCC supporta tramite una funzione intrinseca.

+0

L'ho fatto. Con più di due o tre compilatori può diventare un vero casino, se diversi compilatori usano una sintassi diversa. –