tutorial template else dotliquid all_products c performance switch-statement assembly jump-table

template - products liquid shopify



¿Es ''switch'' más rápido que ''if''? (12)

Aunque no estoy seguro de por qué uno es más rápido y otro es más lento.

En realidad, no es demasiado difícil de explicar ... Si recuerdas que las ramas mal pronosticadas son decenas o cientos de veces más caras que las ramas predichas correctamente.

En la versión % 20 , el primer caso / if es siempre el que golpea. Las CPU modernas "aprenden" qué ramas se toman normalmente y cuáles no, por lo que pueden predecir fácilmente cómo se comportará esta rama en casi todas las iteraciones del bucle. Eso explica por qué la versión "if" vuela; nunca tiene que ejecutar nada más allá de la primera prueba, y (correctamente) predice el resultado de esa prueba para la mayoría de las iteraciones. Obviamente, el "interruptor" se implementa de forma ligeramente diferente, tal vez incluso una tabla de salto, que puede ser lenta gracias a la rama calculada.

En la versión % 21 , las ramas son esencialmente aleatorias. Así que no solo muchos de ellos ejecutan cada iteración, sino que la CPU no puede adivinar en qué dirección irán. Este es el caso donde una tabla de salto (u otra optimización de "cambio") puede ayudar.

Es muy difícil predecir el rendimiento de un fragmento de código con un compilador y una CPU modernos, y se vuelve más difícil con cada generación. El mejor consejo es "ni siquiera te molestes en intentarlo; siempre perfil". Ese consejo mejora, y el conjunto de personas que pueden ignorarlo con éxito se reduce cada año.

Todo lo cual es para decir que mi explicación anterior es en gran parte una conjetura. :-)

¿Es una instrucción de switch realmente más rápida que una instrucción if ?

Ejecuté el siguiente código en el compilador x64 C ++ de Visual Studio 2010 con el indicador /Ox :

#include <stdlib.h> #include <stdio.h> #include <time.h> #define MAX_COUNT (1 << 29) size_t counter = 0; size_t testSwitch() { clock_t start = clock(); size_t i; for (i = 0; i < MAX_COUNT; i++) { switch (counter % 4 + 1) { case 1: counter += 4; break; case 2: counter += 3; break; case 3: counter += 2; break; case 4: counter += 1; break; } } return 1000 * (clock() - start) / CLOCKS_PER_SEC; } size_t testIf() { clock_t start = clock(); size_t i; for (i = 0; i < MAX_COUNT; i++) { const size_t c = counter % 4 + 1; if (c == 1) { counter += 4; } else if (c == 2) { counter += 3; } else if (c == 3) { counter += 2; } else if (c == 4) { counter += 1; } } return 1000 * (clock() - start) / CLOCKS_PER_SEC; } int main() { printf("Starting.../n"); printf("Switch statement: %u ms/n", testSwitch()); printf("If statement: %u ms/n", testIf()); }

y obtuve estos resultados:

Declaración de conmutación: 5261 ms
Si declaración: 5196 ms

Por lo que he aprendido, las declaraciones de switch aparentemente usan tablas de salto para optimizar la bifurcación.

Preguntas:

  1. ¿Cómo sería una tabla de salto básica, en x86 o x64?

  2. ¿Este código está utilizando una tabla de salto?

  3. ¿Por qué no hay diferencia de rendimiento en este ejemplo? ¿Hay alguna situación en la que haya una diferencia de rendimiento significativa?

Desmontaje del código:

testIf: 13FE81B10 sub rsp,48h 13FE81B14 call qword ptr [__imp_clock (13FE81128h)] 13FE81B1A mov dword ptr [start],eax 13FE81B1E mov qword ptr [i],0 13FE81B27 jmp testIf+26h (13FE81B36h) 13FE81B29 mov rax,qword ptr [i] 13FE81B2E inc rax 13FE81B31 mov qword ptr [i],rax 13FE81B36 cmp qword ptr [i],20000000h 13FE81B3F jae testIf+0C3h (13FE81BD3h) 13FE81B45 xor edx,edx 13FE81B47 mov rax,qword ptr [counter (13FE835D0h)] 13FE81B4E mov ecx,4 13FE81B53 div rax,rcx 13FE81B56 mov rax,rdx 13FE81B59 inc rax 13FE81B5C mov qword ptr [c],rax 13FE81B61 cmp qword ptr [c],1 13FE81B67 jne testIf+6Dh (13FE81B7Dh) 13FE81B69 mov rax,qword ptr [counter (13FE835D0h)] 13FE81B70 add rax,4 13FE81B74 mov qword ptr [counter (13FE835D0h)],rax 13FE81B7B jmp testIf+0BEh (13FE81BCEh) 13FE81B7D cmp qword ptr [c],2 13FE81B83 jne testIf+89h (13FE81B99h) 13FE81B85 mov rax,qword ptr [counter (13FE835D0h)] 13FE81B8C add rax,3 13FE81B90 mov qword ptr [counter (13FE835D0h)],rax 13FE81B97 jmp testIf+0BEh (13FE81BCEh) 13FE81B99 cmp qword ptr [c],3 13FE81B9F jne testIf+0A5h (13FE81BB5h) 13FE81BA1 mov rax,qword ptr [counter (13FE835D0h)] 13FE81BA8 add rax,2 13FE81BAC mov qword ptr [counter (13FE835D0h)],rax 13FE81BB3 jmp testIf+0BEh (13FE81BCEh) 13FE81BB5 cmp qword ptr [c],4 13FE81BBB jne testIf+0BEh (13FE81BCEh) 13FE81BBD mov rax,qword ptr [counter (13FE835D0h)] 13FE81BC4 inc rax 13FE81BC7 mov qword ptr [counter (13FE835D0h)],rax 13FE81BCE jmp testIf+19h (13FE81B29h) 13FE81BD3 call qword ptr [__imp_clock (13FE81128h)] 13FE81BD9 sub eax,dword ptr [start] 13FE81BDD imul eax,eax,3E8h 13FE81BE3 cdq 13FE81BE4 mov ecx,3E8h 13FE81BE9 idiv eax,ecx 13FE81BEB cdqe 13FE81BED add rsp,48h 13FE81BF1 ret

testSwitch: 13FE81C00 sub rsp,48h 13FE81C04 call qword ptr [__imp_clock (13FE81128h)] 13FE81C0A mov dword ptr [start],eax 13FE81C0E mov qword ptr [i],0 13FE81C17 jmp testSwitch+26h (13FE81C26h) 13FE81C19 mov rax,qword ptr [i] 13FE81C1E inc rax 13FE81C21 mov qword ptr [i],rax 13FE81C26 cmp qword ptr [i],20000000h 13FE81C2F jae testSwitch+0C5h (13FE81CC5h) 13FE81C35 xor edx,edx 13FE81C37 mov rax,qword ptr [counter (13FE835D0h)] 13FE81C3E mov ecx,4 13FE81C43 div rax,rcx 13FE81C46 mov rax,rdx 13FE81C49 inc rax 13FE81C4C mov qword ptr [rsp+30h],rax 13FE81C51 cmp qword ptr [rsp+30h],1 13FE81C57 je testSwitch+73h (13FE81C73h) 13FE81C59 cmp qword ptr [rsp+30h],2 13FE81C5F je testSwitch+87h (13FE81C87h) 13FE81C61 cmp qword ptr [rsp+30h],3 13FE81C67 je testSwitch+9Bh (13FE81C9Bh) 13FE81C69 cmp qword ptr [rsp+30h],4 13FE81C6F je testSwitch+0AFh (13FE81CAFh) 13FE81C71 jmp testSwitch+0C0h (13FE81CC0h) 13FE81C73 mov rax,qword ptr [counter (13FE835D0h)] 13FE81C7A add rax,4 13FE81C7E mov qword ptr [counter (13FE835D0h)],rax 13FE81C85 jmp testSwitch+0C0h (13FE81CC0h) 13FE81C87 mov rax,qword ptr [counter (13FE835D0h)] 13FE81C8E add rax,3 13FE81C92 mov qword ptr [counter (13FE835D0h)],rax 13FE81C99 jmp testSwitch+0C0h (13FE81CC0h) 13FE81C9B mov rax,qword ptr [counter (13FE835D0h)] 13FE81CA2 add rax,2 13FE81CA6 mov qword ptr [counter (13FE835D0h)],rax 13FE81CAD jmp testSwitch+0C0h (13FE81CC0h) 13FE81CAF mov rax,qword ptr [counter (13FE835D0h)] 13FE81CB6 inc rax 13FE81CB9 mov qword ptr [counter (13FE835D0h)],rax 13FE81CC0 jmp testSwitch+19h (13FE81C19h) 13FE81CC5 call qword ptr [__imp_clock (13FE81128h)] 13FE81CCB sub eax,dword ptr [start] 13FE81CCF imul eax,eax,3E8h 13FE81CD5 cdq 13FE81CD6 mov ecx,3E8h 13FE81CDB idiv eax,ecx 13FE81CDD cdqe 13FE81CDF add rsp,48h 13FE81CE3 ret

Actualizar:

Resultados interesantes here . Aunque no estoy seguro de por qué uno es más rápido y otro es más lento.


¿Cómo sabe que su computadora no estaba realizando alguna tarea no relacionada con la prueba durante el ciclo de prueba de conmutación y realizando menos tareas durante el ciclo de prueba if? Los resultados de su prueba no muestran nada como:

  1. la diferencia es muy pequeña
  2. solo hay un resultado, no una serie de resultados
  3. hay muy pocos casos

Mis resultados:

Agregué:

printf("counter: %u/n", counter);

hasta el final para que no optimice el bucle ya que el contador nunca se usó en su ejemplo, ¿por qué el compilador realiza el bucle? Inmediatamente, el cambio siempre estaba ganando incluso con un micro-punto de referencia.

El otro problema con tu código es:

switch (counter % 4 + 1)

en su bucle de conmutación, frente a

const size_t c = counter % 4 + 1;

en su bucle if. Muy grande diferencia si arreglas eso. Creo que colocar la instrucción dentro de la instrucción switch provoca que el compilador envíe el valor directamente a los registros de la CPU en lugar de ponerlo en la pila primero. Por lo tanto, esto es a favor de la declaración de cambio y no una prueba equilibrada.

Ah, y creo que también deberías reiniciar el contador entre pruebas. De hecho, probablemente deberías usar algún tipo de número aleatorio en lugar de +1, +2, +3, etc., ya que probablemente optimizará algo allí. Por número aleatorio, me refiero a un número basado en la hora actual, por ejemplo. De lo contrario, el compilador podría convertir ambas funciones en una operación matemática larga y ni siquiera molestarse con ningún bucle.

He modificado el código de Ryan lo suficiente para asegurarme de que el compilador no pudiera resolver las cosas antes de que se ejecutara el código:

#include <stdlib.h> #include <stdio.h> #include <time.h> #define MAX_COUNT (1 << 26) size_t counter = 0; long long testSwitch() { clock_t start = clock(); size_t i; for (i = 0; i < MAX_COUNT; i++) { const size_t c = rand() % 20 + 1; switch (c) { case 1: counter += 20; break; case 2: counter += 33; break; case 3: counter += 62; break; case 4: counter += 15; break; case 5: counter += 416; break; case 6: counter += 3545; break; case 7: counter += 23; break; case 8: counter += 81; break; case 9: counter += 256; break; case 10: counter += 15865; break; case 11: counter += 3234; break; case 12: counter += 22345; break; case 13: counter += 1242; break; case 14: counter += 12341; break; case 15: counter += 41; break; case 16: counter += 34321; break; case 17: counter += 232; break; case 18: counter += 144231; break; case 19: counter += 32; break; case 20: counter += 1231; break; } } return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC; } long long testIf() { clock_t start = clock(); size_t i; for (i = 0; i < MAX_COUNT; i++) { const size_t c = rand() % 20 + 1; if (c == 1) { counter += 20; } else if (c == 2) { counter += 33; } else if (c == 3) { counter += 62; } else if (c == 4) { counter += 15; } else if (c == 5) { counter += 416; } else if (c == 6) { counter += 3545; } else if (c == 7) { counter += 23; } else if (c == 8) { counter += 81; } else if (c == 9) { counter += 256; } else if (c == 10) { counter += 15865; } else if (c == 11) { counter += 3234; } else if (c == 12) { counter += 22345; } else if (c == 13) { counter += 1242; } else if (c == 14) { counter += 12341; } else if (c == 15) { counter += 41; } else if (c == 16) { counter += 34321; } else if (c == 17) { counter += 232; } else if (c == 18) { counter += 144231; } else if (c == 19) { counter += 32; } else if (c == 20) { counter += 1231; } } return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC; } int main() { srand(time(NULL)); printf("Starting.../n"); printf("Switch statement: %lld ms/n", testSwitch()); fflush(stdout); printf("counter: %d/n", counter); counter = 0; srand(time(NULL)); printf("If statement: %lld ms/n", testIf()); fflush(stdout); printf("counter: %d/n", counter); }

interruptor: 3740
si: 3980

(resultados similares en múltiples intentos)

También reduje el número de casos / ifs a 5 y la función de cambio todavía ganó.


A tu pregunta:

1. ¿Qué aspecto tendría una tabla de salto básica, en x86 o x64?

La tabla de saltos es una dirección de memoria que mantiene el puntero a las etiquetas en algo así como estructura de matriz. El siguiente ejemplo te ayudará a entender cómo se ve la tabla de saltos.

00B14538 D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 Ø.«.Ø.«.Ø.«.Ø.«. 00B14548 D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 00 00 00 00 Ø.«.Ø.«.Ø.«..... 00B14558 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00B14568 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

Donde 00B14538 es el puntero a la tabla de Salto, y un valor como D8 09 AB 00 representa el puntero de la etiqueta.

2.¿Es este código usando una tabla de salto? No en este caso.

3. ¿Por qué no hay diferencia de rendimiento en este ejemplo?

No hay diferencia de rendimiento porque la instrucción para ambos casos se ve igual, no hay tabla de salto.

4. ¿Existe alguna situación en la que haya una diferencia de rendimiento significativa?

Si tiene una secuencia muy larga de verificación, en ese caso, el uso de la tabla de salto reduce el impacto del rendimiento, pero eso conlleva el costo de la memoria.

Lema: El compilador es lo suficientemente inteligente como para manejar este caso :)


Aquí hay algunos resultados del antiguo benchmark bench ++ (ahora difícil de encontrar):

Test Name: F000003 Class Name: Style CPU Time: 0.781 nanoseconds plus or minus 0.0715 Wall/CPU: 1.00 ratio. Iteration Count: 1677721600 Test Description: Time to test a global using a 2-way if/else if statement compare this test with F000004 Test Name: F000004 Class Name: Style CPU Time: 1.53 nanoseconds plus or minus 0.0767 Wall/CPU: 1.00 ratio. Iteration Count: 1677721600 Test Description: Time to test a global using a 2-way switch statement compare this test with F000003 Test Name: F000005 Class Name: Style CPU Time: 7.70 nanoseconds plus or minus 0.385 Wall/CPU: 1.00 ratio. Iteration Count: 1677721600 Test Description: Time to test a global using a 10-way if/else if statement compare this test with F000006 Test Name: F000006 Class Name: Style CPU Time: 2.00 nanoseconds plus or minus 0.0999 Wall/CPU: 1.00 ratio. Iteration Count: 1677721600 Test Description: Time to test a global using a 10-way switch statement compare this test with F000005 Test Name: F000007 Class Name: Style CPU Time: 3.41 nanoseconds plus or minus 0.171 Wall/CPU: 1.00 ratio. Iteration Count: 1677721600 Test Description: Time to test a global using a 10-way sparse switch statement compare this test with F000005 and F000006

Lo que podemos ver de esto es que (en esta máquina, con este compilador - VC ++ 9.0 x64), cada prueba if toma alrededor de 0.7 nanosegundos. A medida que aumenta el número de pruebas, el tiempo se escala de forma casi perfectamente lineal.

Con la declaración de cambio, casi no hay diferencia en la velocidad entre una prueba de 2 vías y una de 10, siempre que los valores sean densos. La prueba de 10 vías con valores dispersos toma aproximadamente 1,6 veces más tiempo que la prueba de 10 vías con valores densos, pero incluso con valores dispersos, aún es mejor que el doble de la velocidad de 10 vías if / else if .

En pocas palabras: usar solo una prueba de 4 vías realmente no le mostrará mucho sobre el rendimiento de switch vs if / else .Si observa los números de este código, es bastante fácil interpolar el hecho de que para una prueba de 4 vías, esperaríamos que los dos produjeran resultados bastante similares (~ 2.8 nanosegundos para una if/ else, ~ 2.0 para switch).


Contestaré 2) y haré algunos comentarios generales. 2) No, no hay una tabla de salto en el código de ensamblaje que ha publicado. Una tabla de salto es una tabla de destinos de salto y una o dos instrucciones para saltar directamente a una ubicación indexada desde la tabla. Una tabla de salto tendría más sentido cuando hay muchos destinos de cambio posibles. Tal vez el optimizador sepa que simple, si no la lógica es más rápida, a menos que el número de destinos sea mayor que algún umbral. Prueba tu ejemplo otra vez con 20 posibilidades en lugar de 4.


El compilador es libre de compilar la instrucción switch como un código que es equivalente a la instrucción if, o crear una tabla de salto. Es probable que elija uno en el otro en función de lo que se ejecutará más rápido o generará el código más pequeño dependiendo de lo que haya especificado en sus opciones de compilación, por lo que en el peor de los casos será la misma velocidad que las sentencias if

Confiaría en que el compilador haga la mejor elección y se enfoque en lo que hace que el código sea más legible.

Si el número de casos es muy grande, una tabla de salto será mucho más rápida que una serie de if. Sin embargo, si los pasos entre los valores son muy grandes, entonces la tabla de salto puede volverse grande y el compilador puede elegir no generar uno.


Estaba intrigado, y eché un vistazo a lo que podía cambiar con respecto a su ejemplo para que se ejecutara más rápidamente la instrucción switch.

Si llega a 40 declaraciones if y agrega un caso 0, entonces el bloque if se ejecutará más lentamente que la instrucción de cambio equivalente. Tengo los resultados aquí: https://www.ideone.com/KZeCz .

El efecto de eliminar el caso 0 se puede ver aquí: https://www.ideone.com/LFnrX .


Hay varias optimizaciones que un compilador puede hacer en un switch. No creo que la "tabla de salto" mencionada a menudo sea muy útil, ya que solo funciona cuando la entrada se puede delimitar de alguna manera.

C El pseudocódigo para una "tabla de salto" sería algo como this : tenga en cuenta que el compilador en la práctica necesitará insertar alguna forma de if test alrededor de la tabla para asegurarse de que la entrada fuera válida en la tabla. Tenga en cuenta también que solo funciona en el caso específico de que la entrada es una ejecución de números consecutivos.

Si el número de sucursales en un switch es extremadamente grande, un compilador puede hacer cosas como usar la búsqueda binaria en los valores del switch, lo que (en mi opinión) sería una optimización mucho más útil, ya que aumenta significativamente el rendimiento en algunos los escenarios, es tan general como lo es un switch, y no resulta en un mayor tamaño de código generado. Pero para ver eso, su código de prueba necesitaría MUCHO más sucursales para ver cualquier diferencia.

Para responder a sus preguntas específicas:

  1. Clang genera uno que se ve así:

    test_switch(char): # @test_switch(char) movl %edi, %eax cmpl $19, %edi jbe .LBB0_1 retq .LBB0_1: jmpq *.LJTI0_0(,%rax,8) jmp void call<0u>() # TAILCALL jmp void call<1u>() # TAILCALL jmp void call<2u>() # TAILCALL jmp void call<3u>() # TAILCALL jmp void call<4u>() # TAILCALL jmp void call<5u>() # TAILCALL jmp void call<6u>() # TAILCALL jmp void call<7u>() # TAILCALL jmp void call<8u>() # TAILCALL jmp void call<9u>() # TAILCALL jmp void call<10u>() # TAILCALL jmp void call<11u>() # TAILCALL jmp void call<12u>() # TAILCALL jmp void call<13u>() # TAILCALL jmp void call<14u>() # TAILCALL jmp void call<15u>() # TAILCALL jmp void call<16u>() # TAILCALL jmp void call<17u>() # TAILCALL jmp void call<18u>() # TAILCALL jmp void call<19u>() # TAILCALL .LJTI0_0: .quad .LBB0_2 .quad .LBB0_3 .quad .LBB0_4 .quad .LBB0_5 .quad .LBB0_6 .quad .LBB0_7 .quad .LBB0_8 .quad .LBB0_9 .quad .LBB0_10 .quad .LBB0_11 .quad .LBB0_12 .quad .LBB0_13 .quad .LBB0_14 .quad .LBB0_15 .quad .LBB0_16 .quad .LBB0_17 .quad .LBB0_18 .quad .LBB0_19 .quad .LBB0_20 .quad .LBB0_21

  2. Puedo decir que no está usando una tabla de saltos - 4 instrucciones de comparación son claramente visibles:

    13FE81C51 cmp qword ptr [rsp+30h],1 13FE81C57 je testSwitch+73h (13FE81C73h) 13FE81C59 cmp qword ptr [rsp+30h],2 13FE81C5F je testSwitch+87h (13FE81C87h) 13FE81C61 cmp qword ptr [rsp+30h],3 13FE81C67 je testSwitch+9Bh (13FE81C9Bh) 13FE81C69 cmp qword ptr [rsp+30h],4 13FE81C6F je testSwitch+0AFh (13FE81CAFh)

    Una solución basada en tabla de saltos no usa comparación en absoluto.

  3. O bien no hay suficientes ramas para hacer que el compilador genere una tabla de salto, o su compilador simplemente no los genera No estoy seguro de cuál.

EDITAR 2014 : se ha discutido en alguna otra parte de personas familiarizadas con el optimizador de LLVM que dicen que la optimización de la tabla de salto puede ser importante en muchos escenarios; por ejemplo, en los casos en que hay una enumeración con muchos valores y muchos casos en contra de los valores en dicha enumeración. Dicho esto, estoy a la altura de lo que dije anteriormente en 2011: con demasiada frecuencia veo a la gente pensar "si hago un cambio, será el mismo momento, no importa cuántos casos tenga", y eso es completamente falso. Incluso con una tabla de saltos, obtiene el costo del salto indirecto y paga las entradas en la tabla para cada caso; y el ancho de banda de la memoria es un gran problema en el hardware moderno.

Escribir código para legibilidad. this


Ninguna.En la mayoría de los casos en los que ingresa al ensamblador y realiza mediciones reales de rendimiento, su pregunta es simplemente la incorrecta. Para el ejemplo dado tu pensamiento es definitivamente demasiado corto ya que

counter += (4 - counter % 4);

Me parece que es la expresión de incremento correcta que deberías usar.


No, estos son si luego saltan o si saltan más ... Una tabla de saltos tendría una tabla de direcciones o usaría un hash o algo así.

Más rápido o más lento es subjetivo. Por ejemplo, podría ser que el caso 1 sea lo último en lugar de lo primero, y si su programa de prueba o su programa del mundo real utilizara el caso 1 la mayoría de las veces, el código sería más lento con esta implementación. Entonces, solo reorganizar la lista de casos, dependiendo de la implementación, puede hacer una gran diferencia.

Si usó los casos 0-3 en lugar de 1-4, el compilador podría haber usado una tabla de salto, el compilador debería haber averiguado la eliminación de su +1 de todos modos. Tal vez fue la pequeña cantidad de artículos. Si lo hubiera hecho 0 - 15 o 0 - 31, por ejemplo, puede haberlo implementado con una tabla o haber usado algún otro método abreviado. El compilador es libre de elegir cómo implementa las cosas siempre que cumpla con la funcionalidad del código fuente. Y esto entra en las diferencias del compilador y las diferencias de versión y las diferencias de optimización. Si desea una tabla de salto, haga una tabla de salto, si desea un árbol de if-then-else haga un árbol de if-then-else. Si desea que el compilador decida, use una instrucción switch / case.


Tenga en cuenta que cuando un conmutador NO se compila en una tabla de salto, muy a menudo puede escribir si es más eficiente que el conmutador ...

(1) si los casos tienen un ordenamiento, en lugar del peor de los casos para todas las N, puede escribir sus "if" para probar si en la mitad superior o inferior, entonces en cada mitad de eso, el estilo de búsqueda binaria ... resultará en el peor de los casos es logN en lugar de N

(2) si ciertos casos / grupos son mucho más frecuentes que otros casos, entonces diseñar su if para aislar esos casos primero puede acelerar el tiempo promedio a través de


Un buen compilador de optimización como MSVC puede generar:

  1. una tabla de salto simple si los casos están dispuestos en un buen rango largo
  2. una tabla de saltos dispersos (de dos niveles) si hay muchos huecos
  3. una serie de ifs si el número de casos es pequeño o los valores no están muy juntos
  4. una combinación de lo anterior si los casos representan varios grupos de rangos muy espaciados.

En resumen, si el switch parece ser más lento que una serie de ifs, el compilador podría convertirlo en uno solo. Y es probable que no sea solo una secuencia de comparaciones para cada caso, sino un árbol de búsqueda binario. Vea here para un ejemplo.