Ventaja de cambiar sobre la declaración if-else

168

¿Cuál es la mejor práctica para usar una switchdeclaración en lugar de usar una ifdeclaración para 30 unsignedenumeraciones donde aproximadamente 10 tienen una acción esperada (que actualmente es la misma acción)? Es necesario tener en cuenta el rendimiento y el espacio, pero no son críticos. He abstraído el fragmento, así que no me odies por las convenciones de nombres.

switch declaración:

// 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 declaración:

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();
}
Gusto-
fuente
26
Esto se edita como 'subjetivo'? De Verdad? ¿Seguramente "subjetivo" es para cosas que no se pueden probar de una forma u otra?
Alexandra Franks
Claro que puede verlo desde el punto en que genera el código más eficiente, pero cualquier compilador moderno debería ser igualmente eficiente. Al final, se trata más del color del cobertizo para bicicletas.
jfs
8
No estoy de acuerdo, no creo que esto sea subjetivo. Una simple diferencia de ASM es importante, en muchos casos no puede ignorar unos segundos de optimización. Y en esta pregunta, no es una guerra o debate religioso, hay una explicación racional de por qué uno sería más rápido, solo lea la respuesta aceptada.
chakrit
1
Que es más rápido: stackoverflow.com/questions/6805026/is-switch-faster-than-if
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@RichardFranks offtopic: ¡gracias! eres el primer humano asumido la moderación en SO que he visto
jungle_mole

Respuestas:

162

Use el interruptor.

En el peor de los casos, el compilador generará el mismo código que una cadena if-else, para que no pierda nada. En caso de duda, coloque los casos más comunes primero en la declaración de cambio.

En el mejor de los casos, el optimizador puede encontrar una mejor manera de generar el código. Las cosas comunes que hace un compilador es construir un árbol de decisión binario (guarda comparaciones y saltos en el caso promedio) o simplemente construir una tabla de salto (funciona sin comparaciones).

Nils Pipenbrinck
fuente
2
Técnicamente, todavía habrá una comparación, para asegurarse de que el valor de la enumeración se encuentre dentro de la tabla de salto.
0124816
Jep Es verdad. Sin embargo, activar las enumeraciones y manejar todos los casos puede eliminar la última comparación.
Nils Pipenbrinck
44
Tenga en cuenta que una serie de ifs podría analizarse teóricamente como un conmutador por un compilador, pero ¿por qué arriesgarse? Al usar un interruptor, se está comunicando exactamente lo que desea, lo que facilita la generación de código.
jakobengblom2
55
jakoben: Eso podría hacerse, pero solo para cadenas de tipo if / else. En la práctica, esto no ocurre porque los programadores usan switch. Investigué la tecnología del compilador y confío en mí: encontrar tales construcciones "inútiles" lleva mucho tiempo. Para los compiladores, tal optimización tiene sentido.
Nils Pipenbrinck
55
@NilsPipenbrinck con la facilidad de la construcción de los pseudo-recursivo if- elsecadenas en la programación plantilla meta, y la dificultad de generar switch casecadenas, que la cartografía puede llegar a ser más importante. (y sí, comentario antiguo, pero la web es para siempre, o al menos hasta el próximo martes)
Yakk - Adam Nevraumont
45

Para el caso especial que ha proporcionado en su ejemplo, el código más claro es probablemente:

if (RequiresSpecialEvent(numError))
    fire_special_event();

Obviamente, esto solo mueve el problema a un área diferente del código, pero ahora tiene la oportunidad de reutilizar esta prueba. También tiene más opciones sobre cómo resolverlo. Puede usar std :: set, por ejemplo:

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

No estoy sugiriendo que esta sea la mejor implementación de RequiereSpecialEvent, solo que es una opción. Todavía puede usar un interruptor o una cadena if-else, o una tabla de búsqueda, o alguna manipulación de bits en el valor, lo que sea. Cuanto más oscuro sea su proceso de decisión, más valor obtendrá de tenerlo en una función aislada.

Mark Ransom
fuente
55
Esto es tan cierto. La legibilidad es mucho mejor que tanto el modificador como las sentencias if. De hecho, iba a responder algo como esto, pero me ganaste. :-)
mlarsen
Si sus valores de enumeración son todos pequeños, entonces no necesita un hash, solo una tabla. ej. const std::bitset<MAXERR> specialerror(initializer); Úselo con if (specialerror[numError]) { fire_special_event(); }. Si desea verificar los límites, bitset::test(size_t)lanzará una excepción en los valores fuera de los límites. ( bitset::operator[]no comprueba el rango). cplusplus.com/reference/bitset/bitset/test . Esto probablemente superará a la implementación de una tabla de salto generada por el compilador switch, especialmente. en el caso no especial donde será una sola rama no tomada.
Peter Cordes
@PeterCordes Sigo argumentando que es mejor poner la tabla en su propia función. Como dije, hay muchas opciones que se abren cuando haces eso, no intenté enumerarlas todas.
Mark Ransom
@ MarkRansom: No quise estar en desacuerdo con abstraerlo. Solo desde que dio una implementación de muestra usando std::set, pensé en señalar que probablemente sea una mala elección. Resulta que gcc ya compila el código del OP para probar un mapa de bits en un bit de 32 bits inmediato. godbolt: goo.gl/qjjv0e . gcc 5.2 incluso hará esto para la ifversión. Además, gcc más reciente utilizará la instrucción de prueba de bits en btlugar de cambiar para colocar un 1bit en el lugar correcto y usar test reg, imm32.
Peter Cordes
Este mapa de bits de constante inmediata es una gran victoria, porque no hay pérdida de caché en el mapa de bits. Funciona si los códigos de error "especiales" están todos en un rango de 64 o menos. (o 32 para el código heredado de 32 bits). El compilador resta el valor del caso más pequeño, si no es cero. La conclusión es que los compiladores recientes son lo suficientemente inteligentes como para que probablemente obtengas un buen código de cualquier lógica que uses, a menos que le digas que use una estructura de datos voluminosa.
Peter Cordes
24

El cambio es más rápido.

Simplemente intente si 30 valores diferentes dentro de un bucle y compárelo con el mismo código usando el interruptor para ver qué tan rápido es el interruptor.

Ahora, el interruptor tiene un problema real : el interruptor debe conocer en tiempo de compilación los valores dentro de cada caso. Esto significa que el siguiente código:

// 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 ;
    }
}

no se compilará

La mayoría de la gente usará define (¡Aargh!), Y otros declararán y definirán variables constantes en la misma unidad de compilación. Por ejemplo:

// 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 ;
    }
}

Entonces, al final, el desarrollador debe elegir entre "velocidad + claridad" versus "acoplamiento de código".

(No es que un interruptor no se pueda escribir para ser confuso como el infierno ... La mayoría del interruptor que veo actualmente son de esta categoría "confusa" ... Pero esta es otra historia ...)

Editar 2008-09-21:

bk1e agregó el siguiente comentario: " Definir constantes como enumeraciones en un archivo de encabezado es otra forma de manejar esto".

Por supuesto que es.

El objetivo de un tipo externo era desacoplar el valor de la fuente. Definir este valor como una macro, como una simple declaración constante o incluso como una enumeración tiene el efecto secundario de incluir el valor. Por lo tanto, si la definición, el valor de enumeración o el valor constante cambian, sería necesaria una recopilación. La declaración externa significa que no es necesario volver a compilar en caso de cambio de valor, pero, por otro lado, hace que sea imposible usar el interruptor. La conclusión es que el uso del interruptor aumentará el acoplamiento entre el código del interruptor y las variables utilizadas como casos . Cuando esté bien, use el interruptor. Cuando no lo es, entonces, no es sorpresa.

.

Editar 2013-01-15:

Vlad Lazarenko comentó mi respuesta, dando un enlace a su estudio en profundidad del código de ensamblaje generado por un interruptor. Muy ilustrativo: http://lazarenko.me/switch/

paercebal
fuente
Definir constantes como enumeraciones en un archivo de encabezado es otra forma de manejar esto.
bk1e
1
@Vlad Lazarenko: ¡Gracias por el enlace! Fue una lectura muy interesante.
paercebal
1
El enlace de @AhmedHussein user404725 está muerto. Afortunadamente, lo encontré en la máquina WayBack: web.archive.org/web/20131111091431/http://lazarenko.me/2013/01/… . De hecho, la máquina WayBack puede ser una gran bendición.
Jack Giffin
Muchas gracias, es muy útil
Ahmed Hussein
20

El compilador lo optimizará de todos modos: elija el interruptor, ya que es el más legible.

Alexandra Franks
fuente
3
Lo más probable es que el compilador no toque if-then-else. De hecho, gccno lo haré con seguridad (hay una buena razón para eso). Clang optimizará ambos casos en una búsqueda binaria. Por ejemplo, mira esto .
7

El Switch, aunque solo sea por legibilidad. Gigante si las declaraciones son más difíciles de mantener y más difíciles de leer en mi opinión.

ERROR_01 : // caída intencional

o

(ERROR_01 == numError) ||

El último es más propenso a errores y requiere más tipeo y formato que el primero.

scubabbl
fuente
6

Código de legibilidad. Si desea saber qué funciona mejor, use un generador de perfiles, ya que las optimizaciones y los compiladores varían, y los problemas de rendimiento rara vez se encuentran donde las personas piensan que están.

Bdoserror
fuente
6

Use el interruptor, es para qué sirve y qué esperan los programadores.

Sin embargo, pondría las etiquetas de casos redundantes, solo para hacer que las personas se sientan cómodas, estaba tratando de recordar cuándo / cuáles son las reglas para dejarlas fuera.
No desea que el próximo programador que trabaje en él tenga que pensar innecesariamente sobre los detalles del idioma (¡podría ser usted dentro de unos meses!)

Martin Beckett
fuente
4

Los compiladores son muy buenos para optimizar switch. El gcc reciente también es bueno para optimizar un montón de condiciones en unif .

Hice algunos casos de prueba en Godbolt .

Cuando el case valores se agrupan juntos, gcc, clang e icc son lo suficientemente inteligentes como para usar un mapa de bits para verificar si un valor es uno de los especiales.

Por ejemplo, gcc 5.2 -O3 compila el switchto (y ifalgo muy similar):

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()

Tenga en cuenta que el mapa de bits son datos inmediatos, por lo que no hay una posible falta de caché de datos para acceder a él, o una tabla de salto.

gcc 4.9.2 -O3 compila el switchen un mapa de bits, pero lo hace 1U<<errNumbercon mov / shift. Recopila la ifversión a series de ramas.

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()

Observe cómo resta 1 de errNumber(con leapara combinar esa operación con un movimiento). Eso le permite ajustar el mapa de bits en un inmediato de 32 bits, evitando el inmediato de 64 bitsmovabsq que requiere más bytes de instrucciones.

Una secuencia más corta (en código máquina) sería:

    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

(La omisión de uso jc fire_special_eventes omnipresente y es un error del compilador ).

rep ret se usa en objetivos de ramificación, y siguiendo ramificaciones condicionales, en beneficio de los viejos AMD K8 y K10 (pre-Bulldozer): ¿Qué significa `rep ret`? . Sin ella, la predicción de rama no funciona tan bien en esas CPU obsoletas.

bt(prueba de bit) con un registro arg es rápido. Combina el trabajo de desplazar a la izquierda un 1 porerrNumber bits y hacer untest , pero sigue siendo una latencia de 1 ciclo y solo una única Intel uop. Es lento con un argumento de memoria debido a su semántica CISC: con un operando de memoria para la "cadena de bits", la dirección del byte a probar se calcula en función del otro argumento (dividido por 8), e isn no se limita al fragmento de 1, 2, 4 u 8 bytes señalado por el operando de memoria.

De las tablas de instrucciones de Agner Fog , una instrucción de cambio de conteo variable es más lenta que una btde Intel reciente (2 uops en lugar de 1, y shift no hace todo lo demás que se necesita).

Peter Cordes
fuente
4

Las ventajas de switch () sobre si el caso else son: 1. El cambio es mucho más eficiente en comparación con if else porque cada caso no depende del caso anterior, a diferencia de lo contrario, donde la declaración individual debe verificarse para determinar si es verdadera o falsa.

  1. Cuando hay un no. de los valores para una sola expresión, el cambio de mayúsculas y minúsculas es más flexible en caso de que si no, porque en caso contrario, el juicio se basa en solo dos valores, es decir, verdadero o falso.

  2. Los valores en el conmutador están definidos por el usuario, mientras que los valores en caso de que sí se basan en restricciones.

  3. En caso de error, las declaraciones en el interruptor pueden verificarse y corregirse fácilmente, lo que es relativamente difícil de verificar en caso de que se haga otra declaración.

  4. La caja del interruptor es mucho más compacta y fácil de leer y comprender.

Vineeth Krishna K
fuente
2

En mi opinión, este es un ejemplo perfecto de para qué se hizo la interrupción del interruptor.

Nescio
fuente
en c # este es el único caso donde ocurre el pensamiento de caída. Buen argumento ahí mismo.
BCS
2

Si es probable que sus casos permanezcan agrupados en el futuro, si más de un caso corresponde a un resultado, el cambio puede resultar más fácil de leer y mantener.

TSomKes
fuente
2

Funcionan igualmente bien. El rendimiento es casi el mismo dado un compilador moderno.

Prefiero las declaraciones if sobre las declaraciones case porque son más legibles y más flexibles: puede agregar otras condiciones que no se basen en la igualdad numérica, como "|| max <min". Pero para el caso simple que publicaste aquí, realmente no importa, solo haz lo que sea más legible para ti.

SquareCog
fuente
2

El interruptor es definitivamente preferido. Es más fácil mirar la lista de casos de un interruptor y saber con certeza lo que está haciendo que leer la condición larga.

La duplicación en la ifcondición es dura para los ojos. Supongamos que uno de los ==fue escrito !=; te darias cuenta? ¿O si una instancia de 'numError' se escribió 'nmuError', que acaba de compilarse?

Generalmente prefiero usar el polimorfismo en lugar del interruptor, pero sin más detalles del contexto, es difícil de decir.

En cuanto al rendimiento, lo mejor es utilizar un generador de perfiles para medir el rendimiento de su aplicación en condiciones similares a las que espera en la naturaleza. De lo contrario, probablemente esté optimizando en el lugar incorrecto y de la manera incorrecta.

Jay Bazuzi
fuente
2

Estoy de acuerdo con la compatibilidad de la solución del interruptor, pero en mi opinión, estás secuestrando el interruptor aquí.
El propósito del interruptor es tener un manejo diferente según el valor.
Si tuviera que explicar su algo en pseudocódigo, usaría un if porque, semánticamente, eso es lo que es: if whatever_error hace esto ...
Entonces, a menos que tenga la intención de cambiar algún día su código para tener un código específico para cada error , Lo usaría si .

Francesca
fuente
2
No estoy de acuerdo, por la misma razón que no estoy de acuerdo con el caso de caída. Leí el cambio como "En los casos 01,07,0A, 10,15,16 y 20 eventos especiales de incendio". No hay caída a otra sección. Esto es solo un artefacto de la sintaxis de C ++ donde repite la palabra clave 'case' para cada valor.
MSalters
1

Elegiría la declaración if en aras de la claridad y la convención, aunque estoy seguro de que algunos estarían en desacuerdo. Después de todo, ¡estás deseando hacer algo ifque sea cierto! Tener un interruptor con una sola acción parece un poco ... innecesario.

William Keller
fuente
1

Yo diría que use SWITCH. De esta manera solo tiene que implementar diferentes resultados. Sus diez casos idénticos pueden usar el predeterminado. Si uno cambia todo lo que necesita es implementar explícitamente el cambio, no es necesario editar el valor predeterminado. También es mucho más fácil agregar o quitar casos de un SWITCH que editar IF y ELSEIF.

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

Tal vez incluso pruebe su condición (en este caso numerror) contra una lista de posibilidades, una matriz tal vez para que su INTERRUPTOR ni siquiera se use a menos que definitivamente haya un resultado.

Luis
fuente
Hay alrededor de 30 errores en total. 10 requieren la acción especial, así que estoy usando el valor predeterminado para los ~ 20 errores que no requieren una acción ...
Zing-
1

Como solo tiene 30 códigos de error, codifique su propia tabla de salto, luego haga todas las elecciones de optimización usted mismo (el salto siempre será más rápido), en lugar de esperar que el compilador haga lo correcto. También hace que el código sea muy pequeño (aparte de la declaración estática de la tabla de salto). También tiene el beneficio adicional de que con un depurador puede modificar el comportamiento en tiempo de ejecución si lo necesita, simplemente introduciendo los datos de la tabla directamente.

Greg Whitfield
fuente
Wow, esa parece ser una forma de convertir un problema simple en uno complejo. ¿Por qué tomarse tantas molestias cuando el compilador hará un gran trabajo por usted? Además, aparentemente es un controlador de errores, por lo que no es probable que sea tan crítico con la velocidad. Un cambio es, con mucho, lo más fácil de leer y mantener.
MrZebra
Una tabla no es compleja, de hecho es probablemente más simple que un cambio de código. Y la declaración mencionó que el rendimiento fue un factor.
Greg Whitfield
Eso suena como una optimización prematura. Siempre que mantenga sus valores de enumeración pequeños y contiguos, el compilador debe hacerlo por usted. Poner el interruptor en una función separada mantiene el código que lo usa agradable y pequeño, como sugiere Mark Ransom en su respuesta, brinda el mismo beneficio de código pequeño.
Peter Cordes
Además, si va a implementar algo usted mismo, haga un std::bitset<MAXERR> specialerror;, entonces if (specialerror[err]) { special_handler(); }. Esto será más rápido que una mesa de salto, especialmente. en el caso no tomado
Peter Cordes
1

No estoy seguro de las mejores prácticas, pero usaría el interruptor, y luego atraparía la falla intencional a través de 'predeterminado'

da5id
fuente
1

Estéticamente tiendo a favorecer este enfoque.

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;
          }
     }
  }

Haga los datos un poco más inteligentes para que podamos hacer la lógica un poco más tonta.

Me doy cuenta de que se ve raro. Aquí está la inspiración (de cómo lo haría en 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()
mbac32768
fuente
44
La sintaxis de un lenguaje no tiene un efecto en cómo ponemos en práctica una solución ... => se ve feo en C y agradable en Python. :)
rlerallut
¿Usar mapas de bits? Si error_0a es 0x0a, etc., podría ponerlos como bits en un largo largo. long long special_events = 1LL << 1 | 1LL << 7 | 1LL << 0xa ... Luego use if (special_events & (1LL << numError) fire_special_event ()
paperhorse
1
Yuck Ha convertido una operación O (1) en el peor de los casos (si se generan tablas de salto) en O (N) en el peor de los casos (donde N es el número de casos manejados), y ha utilizado un breakexterior a case(sí, un menor pecado, pero un pecado no obstante). :)
Mac
¿Qué asco? Dijo que el rendimiento y el espacio no son críticos. Simplemente estaba proponiendo otra forma de ver el problema. Si podemos representar un problema de una manera en que los humanos piensen menos, entonces generalmente no me importa si eso significa que las computadoras tienen que pensar más.
mbac32768
1
while (true) != while (loop)

Probablemente el primero está optimizado por el compilador, eso explicaría por qué el segundo ciclo es más lento al aumentar el conteo de ciclos.

MarioFrost
fuente
Esto parece ser un comentario a la respuesta de McAnix. Ese es solo uno de los problemas con ese intento de sincronización ifvs. switchcomo una condición de final de ciclo en Java.
Peter Cordes
1

Cuando se trata de compilar el programa, no sé si hay alguna diferencia. Pero en cuanto al programa en sí y a mantener el código lo más simple posible, personalmente creo que depende de lo que quieras hacer. if else if else, las declaraciones tienen sus ventajas, que creo son:

le permite probar una variable contra rangos específicos; puede usar funciones (Biblioteca estándar o Personal) como condicionales.

(ejemplo:

`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";

Sin embargo, las declaraciones de If else if else pueden volverse complicadas y desordenadas (a pesar de sus mejores intentos) a toda prisa. Las declaraciones de cambio tienden a ser más claras, más limpias y más fáciles de leer; pero solo se puede usar para probar valores específicos (ejemplo:

`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;

Prefiero las declaraciones if - else if - else, pero realmente depende de usted. Si desea utilizar funciones como las condiciones, o desea probar algo contra un rango, matriz o vector y / o no le importa tratar con la complicada anidación, recomendaría usar bloques If if if else. Si desea probar con valores individuales o si desea un bloque limpio y fácil de leer, le recomendaría que use bloques de mayúsculas y minúsculas ().

Jordan Effinger
fuente
0

No soy la persona que le dirá sobre la velocidad y el uso de la memoria, pero mirar una declaración de interruptor es mucho más fácil de entender que una declaración if grande (especialmente 2-3 meses más adelante)

Ed Brown
fuente
0

Sé que es viejo pero

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);
}
}

Variar el recuento de bucles cambia mucho:

Mientras que si / si no: 5ms Switch: 1ms Max Loops: 100000

Mientras que si / si no: 5ms Switch: 3ms Max Loops: 1000000

Mientras que si / si no: 5ms Switch: 14ms Max Loops: 10000000

Mientras que si / si no: 5ms Switch: 149ms Max Loops: 100000000

(agregue más declaraciones si lo desea)

McAnix
fuente
3
Buen punto, pero sry, amigo, estás en el idioma equivocado. Variar el idioma cambia mucho;)
Gabriel Schreiber
2
¿El if(max) breakciclo se ejecuta en tiempo constante independientemente del recuento de ciclos? Parece que el compilador JIT es lo suficientemente inteligente como para optimizar el bucle de distancia counter2=max. Y tal vez sea más lento que cambiar si la primera llamada currentTimeMillistiene más sobrecarga, porque ¿aún no está todo compilado JIT? Poner los bucles en el otro orden probablemente daría resultados diferentes.
Peter Cordes