Vantaggio di passare all'istruzione if-else


168

Qual è la migliore pratica per l'utilizzo di switchun'istruzione rispetto all'utilizzo di ifun'istruzione per 30 unsignedenumerazioni in cui circa 10 hanno un'azione prevista (che attualmente è la stessa azione). Prestazioni e spazio devono essere considerati ma non sono critici. Ho estratto il frammento, quindi non odiarmi per le convenzioni di denominazione.

switch dichiarazione:

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
{  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  {
     fire_special_event();
  }
  break;

  default:
  {
    // error codes that require no additional action
  }
  break;       
}

if dichiarazione:

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))
{
  fire_special_event();
}

26
Questo è modificato come 'soggettivo'? Veramente? Sicuramente "soggettivo" è per cose che non possono essere provate in un modo o nell'altro?
Alexandra Franks,

Sicuramente puoi vederlo dal punto in cui genera il codice più efficiente, ma qualsiasi compilatore moderno dovrebbe essere altrettanto efficiente. Alla fine, questa è più una questione di colore del deposito biciclette.
jfs,

8
Non sono d'accordo, non penso che sia soggettivo. Una semplice differenza di ASM è importante, in molti casi non puoi semplicemente ignorare alcuni secondi di ottimizzazione. E in questa domanda, non è una guerra o un dibattito religioso, c'è una spiegazione razionale del perché uno dovrebbe essere più veloce, basta leggere la risposta accettata.
chakrit,


@RichardFranks offtopic: grats! sei il primo umano a
subire

Risposte:


162

Usa l'interruttore.

Nel peggiore dei casi il compilatore genererà lo stesso codice di una catena if-else, quindi non perderai nulla. In caso di dubbi, inserire prima i casi più comuni nell'istruzione switch.

Nel migliore dei casi l'ottimizzatore potrebbe trovare un modo migliore per generare il codice. Le cose comuni che un compilatore fa è costruire un albero decisionale binario (salva confronti e salti nel caso medio) o semplicemente costruisce una tabella di salto (funziona senza confronti).


2
Tecnicamente ci sarà ancora un confronto, per assicurarsi che il valore dell'enum risieda nella tabella di salto.
0124816

Jep. È vero. L'attivazione di enumerazioni e la gestione di tutti i casi possono tuttavia eliminare l'ultimo confronto.
Nils Pipenbrinck,

4
Si noti che una serie di if potrebbe teoricamente essere analizzata come lo stesso switch di un compilatore, ma perché rischiare? Usando un interruttore, stai comunicando esattamente ciò che desideri, il che rende più semplice la generazione del codice.
jakobengblom2,

5
jakoben: Questo potrebbe essere fatto, ma solo per catene if / else simili a switch. In pratica questi non si verificano perché i programmatori usano switch. Ho approfondito la tecnologia del compilatore e mi fido di me: trovare tali costrutti "inutili" richiede molto tempo. Per i compilatori tale ottimizzazione non ha senso.
Nils Pipenbrinck,

5
@NilsPipenbrinck con la facilità di costruzione di pseudo-recursive if- elsecatene nel modello di programmazione meta, e la difficoltà di generare switch casecatene, che la mappatura può diventare più importante. (e sì, commento antico, ma il web è per sempre, o almeno fino al prossimo martedì)
Yakk - Adam Nevraumont

45

Per il caso speciale che hai fornito nel tuo esempio, il codice più chiaro è probabilmente:

if (RequiresSpecialEvent(numError))
    fire_special_event();

Ovviamente questo sposta il problema in una diversa area del codice, ma ora hai l'opportunità di riutilizzare questo test. Hai anche più opzioni su come risolverlo. È possibile utilizzare std :: set, ad esempio:

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

Non sto suggerendo che questa sia la migliore implementazione di RequispecialEvent, solo che è un'opzione. Puoi comunque usare un interruttore o una catena if-else, una tabella di ricerca o qualche manipolazione di bit sul valore, qualunque cosa. Più oscuro diventa il tuo processo decisionale, maggiore sarà il valore che deriverai dall'avere in una funzione isolata.


5
Questo è così vero La leggibilità è molto meglio di entrambe le istruzioni switch e if. In realtà stavo per rispondere a qualcosa del genere da solo, ma mi hai battuto. :-)
mlarsen,

Se i tuoi valori enum sono tutti piccoli, allora non hai bisogno di un hash, solo di una tabella. es. const std::bitset<MAXERR> specialerror(initializer); usalo con if (specialerror[numError]) { fire_special_event(); }. Se si desidera il controllo dei limiti, bitset::test(size_t)verrà generata un'eccezione per i valori fuori limite. ( bitset::operator[]non verifica il range). cplusplus.com/reference/bitset/bitset/test . Ciò probabilmente supererà l'implementazione di una tabella di salto generata dal compilatore switch, esp. nel caso non speciale in cui questo sarà un singolo ramo non preso.
Peter Cordes,

@PeterCordes Continuo a sostenere che è meglio mettere la tabella nella propria funzione. Come ho detto, ci sono molte opzioni che si aprono quando lo fai, non ho provato a elencarle tutte.
Mark Ransom,

@MarkRansom: non intendevo essere in disaccordo con l'astrattarlo. Proprio da quando hai fornito un'implementazione di esempio utilizzando std::set, ho pensato di sottolineare che probabilmente è una scelta sbagliata. Si scopre che gcc già compila il codice dell'OP per testare una bitmap in un immediato a 32 bit. godbolt: goo.gl/qjjv0e . gcc 5.2 lo farà anche per la ifversione. Inoltre, gcc più recente utilizzerà le istruzioni bit-test btinvece di spostare per mettere un 1po 'nel posto giusto e usando test reg, imm32.
Peter Cordes,

Questa bitmap a costante immediata è una grande vittoria, perché non c'è cache miss sulla bitmap. Funziona se i codici di errore "speciali" sono tutti compresi nell'intervallo 64 o meno. (o 32 per il codice a 32 bit legacy). Il compilatore sottrae il valore del caso più piccolo, se è diverso da zero. La cosa da asporto è che i compilatori recenti sono abbastanza intelligenti che probabilmente otterrai un buon codice da qualsiasi logica usi, a meno che tu non gli dica di usare una struttura di dati ingombrante.
Peter Cordes,

24

L'interruttore è più veloce.

Basta provare se / altro-ing 30 valori diversi all'interno di un ciclo e confrontarlo con lo stesso codice usando switch per vedere quanto è più veloce lo switch.

Ora, lo switch ha un vero problema : lo switch deve conoscere in fase di compilazione i valori all'interno di ciascun caso. Ciò significa che il seguente codice:

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

non verrà compilato.

La maggior parte delle persone userà poi definisce (Aargh!), E altri dichiareranno e definiranno variabili costanti nella stessa unità di compilazione. Per esempio:

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

Quindi, alla fine, lo sviluppatore deve scegliere tra "velocità + chiarezza" e "accoppiamento del codice".

(Non che un interruttore non possa essere scritto per essere confuso come l'inferno ... La maggior parte degli interruttori che vedo attualmente sono di questa categoria "confusa" ... Ma questa è un'altra storia ...)

Modifica 21/09/2008:

bk1e ha aggiunto il seguente commento: " Definire le costanti come enumerazioni in un file di intestazione è un altro modo di gestirlo".

Ovviamente è.

Il punto di un tipo esterno era di disaccoppiare il valore dalla sorgente. La definizione di questo valore come macro, come semplice dichiarazione const int o anche come enum ha l'effetto collaterale di incorporare il valore. Pertanto, se la definizione, il valore enum o il valore const int dovessero cambiare, sarebbe necessaria una ricompilazione. La dichiarazione esterna indica che non è necessario ricompilare in caso di modifica del valore, ma d'altra parte rende impossibile utilizzare switch. La conclusione che si utilizza Using switch aumenterà l'accoppiamento tra il codice switch e le variabili utilizzate come casi . Quando è OK, quindi utilizzare l'interruttore. Quando non lo è, quindi, nessuna sorpresa.

.

Modifica 15-01-2013:

Vlad Lazarenko ha commentato la mia risposta, fornendo un collegamento al suo studio approfondito del codice assembly generato da un interruttore. Molto illuminante: http://lazarenko.me/switch/


Definire le costanti come enumerazioni in un file di intestazione è un altro modo per gestirlo.
bk1e,

6
Passare non è sempre più veloce .

1
@Vlad Lazarenko: grazie per il link! È stata una lettura molto interessante.
Paercebal,

1
@AhmedHussein Il link dell'utente404725 è morto. Per fortuna, l'ho trovato nella WayBack Machine: web.archive.org/web/20131111091431/http://lazarenko.me/2013/01/… . In effetti, la WayBack Machine può essere una vera benedizione.
Jack Giffin,

Grazie mille, è molto utile
Ahmed Hussein,

20

Il compilatore lo ottimizzerà comunque: scegli lo switch poiché è il più leggibile.


3
È probabile che il compilatore non tocchi if-then-else. Infatti,gcc non lo farà di sicuro (c'è una buona ragione per questo). Clang ottimizzerà entrambi i casi in una ricerca binaria. Ad esempio, vedi questo .

7

Lo Switch, se non altro per leggibilità. Secondo me, le dichiarazioni giganti sono più difficili da mantenere e più difficili da leggere.

ERRORE_01 : // fall-through intenzionale

o

(ERRORE_01 == numError) ||

Il successivo è più soggetto a errori e richiede più digitazione e formattazione rispetto al primo.


6

Codice per leggibilità. Se vuoi sapere cosa funziona meglio, usa un profiler, poiché le ottimizzazioni e i compilatori variano e raramente i problemi di prestazioni sono dove le persone pensano di essere.


6

Usa switch, è a cosa serve e cosa si aspettano i programmatori.

Metterei comunque le etichette ridondanti del case - solo per far sentire le persone a proprio agio, stavo cercando di ricordare quando / quali sono le regole per lasciarle fuori.
Non vuoi che il prossimo programmatore che ci sta lavorando debba fare inutili riflessioni sui dettagli della lingua (potresti essere tu tra qualche mese!)


4

I compilatori sono davvero bravi a ottimizzare switch. Gcc recente è anche bravo a ottimizzare un sacco di condizioni in un if.

Ho realizzato alcuni casi di prova su godbolt .

Quando il case valori sono raggruppati vicini, gcc, clang e icc sono tutti abbastanza intelligenti da usare una bitmap per verificare se un valore è uno di quelli speciali.

ad esempio gcc 5.2 -O3 compila il switchto (e ifqualcosa di molto simile):

errhandler_switch(errtype):  # gcc 5.2 -O3
    cmpl    $32, %edi
    ja  .L5
    movabsq $4301325442, %rax   # highest set bit is bit 32 (the 33rd bit)
    btq %rdi, %rax
    jc  .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Si noti che la bitmap è un dato immediato, quindi non ci sono potenziali errori nella cache dei dati ad accedervi o una tabella di salto.

gcc 4.9.2 -O3 compila switchin una bitmap, ma fa 1U<<errNumbercon mov / shift. Compila la ifversione in serie di rami.

errhandler_switch(errtype):  # gcc 4.9.2 -O3
    leal    -1(%rdi), %ecx
    cmpl    $31, %ecx    # cmpl $32, %edi  wouldn't have to wait an extra cycle for lea's output.
              # However, register read ports are limited on pre-SnB Intel
    ja  .L5
    movl    $1, %eax
    salq    %cl, %rax   # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
    testl   $2150662721, %eax
    jne .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Nota come sottrae 1 da errNumber(con leaper combinare quell'operazione con una mossa). Ciò consente di adattare la bitmap in un immediato a 32 bit, evitando l'immediato a 64 bitmovabsq che richiede più byte di istruzione.

Una sequenza più breve (nel codice macchina) sarebbe:

    cmpl    $32, %edi
    ja  .L5
    mov     $2150662721, %eax
    dec     %edi   # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
    bt     %edi, %eax
    jc  fire_special_event
.L5:
    ret

(Il mancato utilizzo jc fire_special_eventè onnipresente ed è un bug del compilatore .)

rep retviene utilizzato nei target delle filiali e nei seguenti rami condizionati, a beneficio dei vecchi AMD K8 e K10 (pre-bulldozer): cosa significa `rep ret`? . Senza di essa, la previsione delle filiali non funziona altrettanto bene su quelle CPU obsolete.

bt(bit test) con un registro arg è veloce. Combina il lavoro di spostare a sinistra un 1 per errNumberbit e fare untest , ma è ancora 1 ciclo di latenza e solo un singolo Intel UOP. È lento con un arg di memoria a causa della sua semantica CISC troppo lunga: con un operando di memoria per la "stringa di bit", l'indirizzo del byte da testare viene calcolato in base all'altro arg (diviso per 8), e isn non si limita al blocco di 1, 2, 4 o 8 byte a cui punta l'operando di memoria.

Dalle tabelle di istruzioni di Agner Fog, un'istruzione di spostamento a conteggio variabile è più lenta di una btrecente Intel (2 uops anziché 1 e shift non fa tutto il necessario).


4

I vantaggi di switch () rispetto a if else case sono: - 1. Switch è molto più efficiente rispetto a if else perché ogni caso non dipende dal caso precedente, diversamente dall'altro caso in cui è necessario controllare la singola istruzione per condizione vera o falsa.

  1. Quando ci sono un no. di valori per una singola espressione, switch case è più flessibile su if else perché in if else il giudizio si basa solo su due valori, ovvero true o false.

  2. I valori nello switch sono definiti dall'utente, mentre i valori in caso contrario si basano su vincoli.

  3. In caso di errore, le istruzioni in switch possono essere facilmente verificate e corrette, il che è relativamente difficile da verificare in caso di istruzione if else.

  4. La custodia è molto più compatta e facile da leggere e comprendere.


2

IMO questo è un perfetto esempio di ciò per cui è stato effettuato il passaggio.


in c # questo è l'unico caso in cui accade il pensiero di caduta. Buona discussione proprio lì.
BCS,

2

Se è probabile che i tuoi casi rimangano raggruppati in futuro - se più di un caso corrisponde a un risultato - il passaggio potrebbe rivelarsi più facile da leggere e mantenere.


2

Funzionano ugualmente bene. Le prestazioni sono quasi le stesse dato un moderno compilatore.

Preferisco le dichiarazioni if ​​rispetto alle dichiarazioni case perché sono più leggibili e più flessibili: è possibile aggiungere altre condizioni non basate sull'uguaglianza numerica, come "|| max <min". Ma per il semplice caso che hai pubblicato qui, non importa, fai solo ciò che è più leggibile per te.


2

switch è sicuramente preferito. È più facile guardare l'elenco dei casi di uno switch e sapere con certezza cosa sta facendo che leggere la condizione if lunga.

La duplicazione nella ifcondizione è dura per gli occhi. Supponiamo che uno dei sia ==stato scritto !=; noteresti? O se un'istanza di 'numError' è stata scritta 'nmuError', che è appena stata compilata?

Preferirei generalmente usare il polimorfismo al posto dello switch, ma senza ulteriori dettagli sul contesto, è difficile da dire.

Per quanto riguarda le prestazioni, la soluzione migliore è utilizzare un profiler per misurare le prestazioni dell'applicazione in condizioni simili a quelle che ci si aspetta in natura. Altrimenti, probabilmente stai ottimizzando nel posto sbagliato e nel modo sbagliato.


2

Sono d'accordo con la compattezza della soluzione switch ma IMO stai dirottando lo switch qui.
Lo scopo dell'interruttore è avere una gestione diversa a seconda del valore.
Se dovessi spiegare il tuo algo in pseudo-codice, useresti un if perché, semanticamente, è quello che è: se qualunque_error fa questo ...
Quindi, a meno che tu non intenda un giorno cambiare il tuo codice per avere un codice specifico per ogni errore , Vorrei usare se .


2
Non sono d'accordo, per lo stesso motivo per cui non sono d'accordo con il caso fallace. Ho letto l'interruttore come "Nei casi 01,07,0A, 10,15,16 e 20 eventi speciali di fuoco". Non ci sono dubbi in un'altra sezione. Questo è solo un artefatto della sintassi C ++ in cui si ripete la parola chiave 'case' per ciascun valore.
MSalters,

1

Vorrei scegliere l'affermazione if per motivi di chiarezza e convenzione, anche se sono sicuro che alcuni non sarebbero d'accordo. Dopotutto, vuoi fare qualcosa che ifuna condizione è vera! Avere un passaggio con un'azione sembra un po '... non necessario.


1

Direi usare SWITCH. In questo modo devi solo implementare risultati diversi. I tuoi dieci casi identici possono utilizzare il valore predefinito. Se uno cambia tutto ciò che serve è implementare esplicitamente la modifica, non è necessario modificare l'impostazione predefinita. È anche molto più semplice aggiungere o rimuovere casi da un INTERRUTTORE piuttosto che modificare IF e ELSEIF.

switch(numerror){
    ERROR_20 : { fire_special_event(); } break;
    default : { null; } break;
}

Forse anche testare la tua condizione (in questo caso numerror) rispetto a un elenco di possibilità, un array forse in modo che il tuo SWITCH non sia nemmeno usato a meno che non ci sia sicuramente un risultato.


Ci sono circa 30 errori in totale. 10 richiedono l'azione speciale, quindi sto usando l'impostazione predefinita per gli errori ~ 20 che non richiedono un'azione ...
Zing

1

Visto che hai solo 30 codici di errore, codifica la tua tabella di salto, quindi fai tu stesso tutte le scelte di ottimizzazione (il salto sarà sempre il più veloce), piuttosto che sperare che il compilatore faccia la cosa giusta. Inoltre rende il codice molto piccolo (a parte la dichiarazione statica della tabella di salto). Ha anche il vantaggio secondario che con un debugger è possibile modificare il comportamento in fase di esecuzione in caso di necessità, semplicemente inserendo direttamente i dati della tabella.


Wow, sembra un modo per trasformare un semplice problema in uno complesso. Perché andare a tutti quei problemi quando il compilatore farà un ottimo lavoro per te. Inoltre è apparentemente un gestore di errori, quindi non è probabile che sia così critico per la velocità. Uno switch è di gran lunga la cosa più semplice da leggere e mantenere.
MrZebra,

Una tabella è difficilmente complessa - in effetti è probabilmente più semplice di un passaggio al codice. E la dichiarazione menzionava che le prestazioni erano un fattore.
Greg Whitfield,

Sembra un'ottimizzazione prematura. Finché mantieni piccoli e contigui i valori di enum, il compilatore dovrebbe farlo per te. Mettere l'interruttore in una funzione separata mantiene il codice che lo usa bello e piccolo, come suggerisce Mark Ransom nella sua risposta, offre lo stesso vantaggio di piccolo codice.
Peter Cordes,

Inoltre, se hai intenzione di implementare qualcosa da solo, crea un std::bitset<MAXERR> specialerror;, allora if (specialerror[err]) { special_handler(); }. Questo sarà più veloce di una tabella di salto, esp. nel caso non preso.
Peter Cordes,

1

Non sono sicuro delle migliori pratiche, ma userei switch - e quindi intrappolerei il fall-through intenzionale tramite 'default'


1

Esteticamente tendo a favorire questo approccio.

unsigned int special_events[] = {
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20
 };
 int special_events_length = sizeof (special_events) / sizeof (unsigned int);

 void process_event(unsigned int numError) {
     for (int i = 0; i < special_events_length; i++) {
         if (numError == special_events[i]) {
             fire_special_event();
             break;
          }
     }
  }

Rendi i dati un po 'più intelligenti in modo da rendere la logica un po' più stupida.

Mi rendo conto che sembra strano. Ecco l'ispirazione (da come lo farei in Python):

special_events = [
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20,
    ]
def process_event(numError):
    if numError in special_events:
         fire_special_event()

4
La sintassi di un linguaggio non hanno un effetto sul modo in cui implementare una soluzione ... => Sembra brutto in C e piacevole in Python. :)
rlerallut,

Usa bitmap? Se error_0a è 0x0a ecc, potresti metterli come bit in un lungo tempo. long long special_events = 1LL << 1 | 1LL << 7 | 1LL << 0xa ... Quindi utilizza if (special_events & (1LL << numError) fire_special_event ()
paperhorse,

1
Che schifo. Hai trasformato un'operazione O (1) nel caso peggiore (se vengono generate tabelle di salto) nel caso peggiore O (N) (dove N è il numero di casi gestiti) e hai utilizzato un carattere breakesterno a case(sì, un minore peccato, ma comunque peccato). :)
Mac,

Che schifo? Ha detto che le prestazioni e lo spazio non sono fondamentali. Stavo semplicemente proponendo un altro modo di vedere il problema. Se riusciamo a rappresentare un problema in un modo in cui gli umani riescono a pensare di meno, di solito non mi interessa se ciò significa che i computer devono pensare di più.
mbac32768,

1
while (true) != while (loop)

Probabilmente il primo è ottimizzato dal compilatore, il che spiegherebbe perché il secondo loop è più lento quando si aumenta il conteggio dei loop.


Questo sembra essere un commento alla risposta di McAnix. Questo è solo uno dei problemi con quel tentativo di temporizzazione ifvs. switchcome condizione di fine del ciclo in Java.
Peter Cordes,

1

Quando si tratta di compilare il programma, non so se ci siano differenze. Ma per quanto riguarda il programma stesso e mantenere il codice il più semplice possibile, penso personalmente che dipenda da cosa vuoi fare. if else if else dichiarazioni hanno i loro vantaggi, che penso siano:

consentono di testare una variabile rispetto a intervalli specifici che è possibile utilizzare le funzioni (Libreria standard o Personale) come condizionali.

(esempio:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 if( a > 0 && a < 5)
   {
     cout<<"a is between 0, 5\n";

   }else if(a > 5 && a < 10)

     cout<<"a is between 5,10\n";

   }else{

       "a is not an integer, or is not in range 0,10\n";

Tuttavia, If else if else le istruzioni possono diventare complicate e disordinate (nonostante i tuoi migliori tentativi) in fretta. Le istruzioni di commutazione tendono ad essere più chiare, più pulite e più facili da leggere; ma può essere utilizzato solo per testare valori specifici (esempio:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 switch(a)
 {
    case 0:
    case 1:
    case 2: 
    case 3:
    case 4:
    case 5:
        cout<<"a is between 0,5 and equals: "<<a<<"\n";
        break;
    //other case statements
    default:
        cout<<"a is not between the range or is not a good value\n"
        break;

Preferisco le dichiarazioni if ​​- else if - else, ma dipende davvero da te. Se vuoi usare le funzioni come condizioni, o vuoi testare qualcosa su un intervallo, un array o un vettore e / o non ti dispiace affrontare il complicato annidamento, ti consiglio di usare If else se else blocchi. Se si desidera verificare valori singoli o si desidera un blocco pulito e di facile lettura, si consiglia di utilizzare i blocchi maiuscole switch ().


0

Non sono la persona che ti dice della velocità e dell'utilizzo della memoria, ma guardare uno stato di commutazione è molto più facile da capire quindi una grande istruzione if (specialmente 2-3 mesi lungo la linea)


0

So che è vecchio ma

public class SwitchTest {
static final int max = 100000;

public static void main(String[] args) {

int counter1 = 0;
long start1 = 0l;
long total1 = 0l;

int counter2 = 0;
long start2 = 0l;
long total2 = 0l;
boolean loop = true;

start1 = System.currentTimeMillis();
while (true) {
  if (counter1 == max) {
    break;
  } else {
    counter1++;
  }
}
total1 = System.currentTimeMillis() - start1;

start2 = System.currentTimeMillis();
while (loop) {
  switch (counter2) {
    case max:
      loop = false;
      break;
    default:
      counter2++;
  }
}
total2 = System.currentTimeMillis() - start2;

System.out.println("While if/else: " + total1 + "ms");
System.out.println("Switch: " + total2 + "ms");
System.out.println("Max Loops: " + max);

System.exit(0);
}
}

Variando il conteggio dei loop cambia molto:

Mentre if / else: 5ms Switch: 1ms Max Loop: 100000

Mentre if / else: 5ms Switch: 3ms Max Loop: 1000000

Mentre if / else: 5ms Switch: 14ms Max Loop: 10000000

Mentre if / else: 5ms Switch: 149ms Max Loop: 100000000

(aggiungi più istruzioni se vuoi)


3
Buon punto, ma sry, amico, sei nella lingua sbagliata. Variando la lingua cambia molto;)
Gabriel Schreiber,

2
Il if(max) breakloop funziona a tempo costante indipendentemente dal conteggio dei loop? Sembra che il compilatore JIT sia abbastanza intelligente da ottimizzare il loop counter2=max. E forse è più lento del passaggio se la prima chiamata a currentTimeMillisha più overhead, perché non è ancora stato compilato tutto JIT? Mettere i loop nell'altro ordine probabilmente darebbe risultati diversi.
Peter Cordes,
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.