Switch vs If Else: quale conviene utilizzare?
Esiste un metodo da seguire per scrivere codice più performante?

All’apparenza entrambe le dichiarazioni sembrano analoghe, in realtà in questo articolo andiamo ad analizzare le differenze tra i due costrutti così da capire quando è conveniente utilizzarle.

Nell’articolo faremo riferimento al linguaggio di programmazione C.

L’operatore If Else

Partiamo con presentare la dichiarazione if else. Questo tipo di istruzione condizionale ci permette di deviare il flusso di un programma dipendentemente dal valore di una condizione che viene posta all’inizio della dichiarazione:

Copy to Clipboard

Nel codice che vedi qui sopra abbiamo implementato una semplice funzione che calcola il valore assoluto di una sottrazione.

In ingresso abbiamo due numeri, x e y (riga 4). Nella condizione principale controlliamo che x sia minore di y (riga 7), se è così effettuiamo la sottrazione di y – x (riga 9), altrimenti eseguiamo il contrario (riga 13).

Terminiamo l’esecuzione del codice restituendo il risultato calcolato (riga 15).

Per comprendere a fondo cosa viene effettivamente eseguito dal computer una volta compilato questo semplice estratto, è necessario passare ad analizzare il codice assembly generato dal compilatore.

Copy to Clipboard

Come puoi vedere, le istruzioni eseguite a basso livello sono divise in due parti principali:

  1. absdiff_se: il corpo iniziale della funzione dove viene effettuata la comparazione tra x e y e viene deciso cosa eseguire. Se x risulta essere minore di y si prosegue con le righe dalla 4 alla 7, effettuando la sottrazione y – x e restituendo il risultato.
  2. .L2: la parte di codice che si occupa di gestire l’else. A queste istruzioni ci si arriva solo tramite il salto condizionale della riga 3, il quale viene eseguito se e solo se x risulta essere maggiore o uguale a y. In tal caso si va alle righe dalla 9 alla 12, prima calcolando x – y e poi restituendo il risultato.

Questo tipo di struttura viene riprodotta dai compilatori ogni qual volta ci troviamo di fronte a if-else strutturati. Immagina ad esempio casi in cui hai molte condizioni in cascata, quello che viene fatto è gestire il flusso del programma con istruzioni di comparazione tra variabili (riga 2) e salti condizionati (riga 3).

Ottimizzazioni

Quando, come nel caso del codice C presentato, è possibile precalcolare i vari risultati fin da subito, i compilatori tendono infine a preferire la produzione di codice assembly di questo tipo:

Copy to Clipboard

In questo caso, il minor numero di istruzioni e la mancanza di salti condizionati, che rompono la schedulazione delle istruzioni fatta dalla CPU, rendono il codice più efficiente.

Non sempre però è possibile utilizzare tecniche di questo tipo, sopratutto quando il numero di condizioni da gestire sono alte o, peggio, non calcolabili a priori.

L’operatore Switch

Per sopperire a situazioni dove ci sono diverse condizioni da verificare, i linguaggi di programmazione moderni ci vengono in aiuto con l’operatore switch case.

Similmente a quanto visto con l’istruzione if-else, lo switch-case consente di implementare delle decisioni multiple e si basa dunque sul confronto tra il risultato di un’espressione e un insieme di valori costanti.

La parola switch è seguita da un’espressione racchiusa tra parentesi tonde, ad esempio switch (a). In C, il risultato deve essere di tipo int o char.

Il resto del costrutto è costituito dai vari case con a seguire un’espressione (costante), separata utilizzando il simbolo dei due punti da una o più istruzioni.

Copy to Clipboard

Switch vs If Else

Prendendo per riferimento proprio questo scenario, abbiamo gli ingredienti necessari per risolvere il nostro quesito iniziale.

L’operatore switch case, infatti, viene normalmente compilato utilizzando una tecnica che permette di ottimizzare l’esecuzione del codice assembly: la jump table.

Una jump table non è altro che un array dove ciascun elemento i-esimo rappresenta l’indirizzo del segmento di codice in cui è implementata l’azione che il programma dovrebbe eseguire quando l’indice dello switch assume il medesimo valore.

Come fatto già in precedenza, analizziamo il codice assembly del programma C appena mostrato:

Copy to Clipboard

Appare subito chiaro come questa tecnica risulti essere particolarmente performante.

Nella riga 3 si effettua la comparazione tra 106 e l’indice (salvato nel registro %rsi).

Se il valore in questione risulta essere maggiore di 106, ovvero l’indice massimo, si salta direttamente alla locazione .L8 che gestisce il caso default.

Viceversa, si continua e si salta alla locazione memorizzata in .L4[index].

Inutile a dirsi, .L4 è proprio la nostra jump table la cui struttura è mostrata di seguito:

Copy to Clipboard

Ciascun elemento di L4, punta ad una etichetta specifica del codice assembly. Grazie all’istruzione di jump mostrata nella riga 5, quindi, ci è possibile saltare istantaneamente, e con un’unica istruzione di salto, all’etichetta di interesse così da eseguire il codice associato.

Conclusioni

Il titolo di questo articolo recitava: Switch vs If else: quale scegliere? Abbiamo a tal proposito analizzato quello che viene effettivamente eseguito da un calcolatore nel caso di un’istruzione if else e una switch case.

Abbiamo visto come la tecnica della jump table permetta di generare codice molto performante nel caso in cui si abbiano diverse possibilità da considerare.

Il risultato è un compilato dove con una istruzione di salto si riesce a coprire l’intera casistica gestita dall’operatore.

Allo stesso tempo, anche l’istruzione di if else produce codice ottimizzato ma va considerato che ciò avviene se le condizioni in gioco sono di numero limitato e calcolabili a priori.