Se vs. velocità di commutazione


112

Le istruzioni Switch sono in genere più veloci delle istruzioni if-else-if equivalenti (come ad esempio descritto in questo articolo ) a causa delle ottimizzazioni del compilatore.

Come funziona effettivamente questa ottimizzazione? Qualcuno ha una buona spiegazione?



Una possibile buona risposta: dotnetperls.com/if-switch-performance
Babak

Risposte:


185

Il compilatore può creare tabelle di salto dove applicabile. Ad esempio, quando si utilizza il riflettore per esaminare il codice prodotto, si vedrà che per enormi interruttori sulle stringhe, il compilatore genererà effettivamente codice che utilizza una tabella hash per inviarli. La tabella hash utilizza le stringhe come chiavi e i delegati ai casecodici come valori.

Questo ha un tempo di esecuzione asintotico migliore rispetto a molti iftest concatenati ed è effettivamente più veloce anche per relativamente poche stringhe.


6
Buona risposta, interessante per la tabella hash.
BobbyShaftoe

4
In alcuni casi vengono anche convertiti in confronti ad albero. Il ragionamento è alquanto complesso, ma fondamentalmente si riduce alla tabella indiretta che neutralizza i buffer di destinazione del salto della CPU moderna e quindi cancella il predittore di ramo. Ricordo vagamente un articolo a una conferenza del GCC sul codegen per gli interruttori.
olliej

Ciò significa: switch (a) case "x": case "y": case "z": // qualcosa si rompe; } è più veloce di: if (a == "x" || a == "b" || a == "c") // qualcosa di giusto?
yazanpro

qui non abbiamo nidificato se altro, solo OR quindi cosa ne pensi?
yazanpro

@yazanpro Sui vecchi compilatori potenzialmente sì (ma nota che il numero di casi è così piccolo che potrebbe non fare la differenza!). Tuttavia, i compilatori moderni eseguono molte più analisi del codice. Di conseguenza, potrebbero capire che questi due frammenti di codice sono equivalenti e applicare le stesse ottimizzazioni. Ma questa è pura speculazione da parte mia, non so se qualche compilatore lo fa davvero.
Konrad Rudolph

15

Questa è una leggera semplificazione poiché in genere qualsiasi compilatore moderno che incontra una if..else if ..sequenza che potrebbe essere banalmente convertita in un'istruzione switch da una persona, lo farà anche il compilatore. Ma solo per aggiungere ulteriore divertimento, il compilatore non è limitato dalla sintassi, quindi può generare internamente istruzioni tipo "switch" che hanno un mix di intervalli, obiettivi singoli, ecc. E possono (e farlo) sia per switch che per if. .else dichiarazioni.

Comunque, un'estensione alla risposta di Konrad è che il compilatore può generare una tabella di salto, ma non è necessariamente garantito (né desiderabile). Per una serie di motivi le tabelle di salto fanno cose cattive ai predittori di ramo sui processori moderni e le tabelle stesse fanno cose cattive per memorizzare il comportamento nella cache, ad es.

switch(a) { case 0: ...; break; case 1: ...; break; }

Se un compilatore ha effettivamente generato una tabella di salto per questo, sarebbe probabilmente più lento del if..else if..codice di stile alternativo a causa della tabella di salto che sconfigge la previsione del ramo.


4

Le statistiche di assenza di partita potrebbero non essere buone.

Se effettivamente scarichi l'origine, i valori di nessuna corrispondenza sono noti per essere 21, sia nel caso di if che di switch. Un compilatore dovrebbe essere in grado di astrarre, sapendo quale istruzione dovrebbe essere eseguita in ogni momento, e una CPU dovrebbe essere in grado di eseguire correttamente la previsione dei branch.

Il caso più interessante è quando non tutti i casi si interrompono, secondo me, ma potrebbe non essere stato lo scopo dell'esperimento.


4

Le istruzioni switch / case possono essere in genere più veloci di un livello profondo, ma quando inizi a entrare in 2 o più, le istruzioni switch / case iniziano a richiedere 2-3 volte il tempo delle istruzioni if ​​/ else annidate.

Questo articolo contiene alcuni confronti di velocità che evidenziano le differenze di velocità quando tali istruzioni sono nidificate.

Ad esempio, in base ai loro test, codice di esempio come il seguente:

if (x % 3 == 0)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 1)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 2)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;

terminato nella metà del tempo impiegato dall'istruzione switch / case equivalente:

switch (x % 3)
    {
        case 0:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
        case 1:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    case 2:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    default:
        switch (y % 3)
        {
            case 0: total += 3;
                break;
            case 1: total += 2;
                break;
            case 2: total += 1;
                break;
            default: total += 0;
                break;
        }
        break;
    }

Sì, è un esempio rudimentale, ma illustra il punto.

Quindi una conclusione potrebbe essere usare switch / case per tipi semplici che sono profondi solo un livello, ma per confronti più complessi e più livelli annidati utilizzare i classici costrutti if / else?


-1: 1. L'articolo ha completamente ignorato Branch Prediction, 2. gli algoritmi non sono esattamente gli stessi (l'unico if-else sul collegamento è già codificato più ottimizzato) e 3. le differenze trovate sono così piccole che nulla scusa l'uso di un codice corretto e pulito (circa 4 ns in 10.000.000 chiamate tra switch e stesso costrutto if-else)
Trojaner

Quell'esempio non sarà ottimizzato a causa di quanti pochi casi ha il blocco interruttore. Tipicamente dopo 5-6 elementi genera una tabella di salto.
antiduh

0

L'unico vantaggio del caso if over è quando c'è un notevole aumento della frequenza di occorrenza del primo caso.

Non sono sicuro di dove sia esattamente la soglia, ma uso la sintassi delle maiuscole a meno che il primo "quasi sempre" non superi il primo test.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.