29

Cuando descubrí que puedo usar solo valores numéricos en las declaraciones switch de C++, pensé que debería haber una diferencia más profunda entre él y un montón de if-else.¿Cómo compila el cambio en Visual C++ y qué tan optimizado y rápido es?

Por lo tanto me pregunté:

  • (Cómo) switch difieren de if-elseif-elseif en términos de velocidad de ejecución, compilar la optimización del tiempo y la recopilación general? Estoy hablando principalmente de MSVC aquí.

Respuesta

35

Un interruptor a menudo se compila a un salto de la mesa (una comparación para saber qué código se ejecute), o si eso no es posible, el compilador todavía puede cambiar el orden de las comparaciones, con el fin de realizar una búsqueda binaria entre los valores (log N comparaciones). Una cadena if-else es una búsqueda lineal (aunque, supongo, si todos los valores relevantes son constantes integrales en tiempo de compilación, el compilador en principio podría realizar optimizaciones similares).

+0

+1 por buena respuesta. También voy a aprovechar esta oportunidad para menospreciar la cadena if-then-elseif. Más de un 'otro si' es un marcador para DDD (Desorden de déficit de diseño). –

+5

Aquí hay una [implementación de conmutador de C++ en el mundo real] (http://www.codeproject.com/Articles/100473/Something-You-May-Not-Know-About-the-Switch-Statem) para Visual C++. –

+0

Sería bueno ahora si esto se optimiza no si pudieran. – Lothar

8

Las sentencias de cambio son a menudo una fuente común de optimización del compilador. Es decir, cómo se los trata depende de la configuración de optimización que use en su compilador.

La forma más básica (no optimizada) de compilar una instrucción switch es tratarla como una cadena de instrucciones if ... else if .... La manera más común que los compiladores optimizar un interruptor es para convertirlo en un jump table que puede parecer algo como:

if (condition1) goto label1; 
if (condition2) goto label2; 
if (condition3) goto label3; 
else   goto default; 
label1: 
    <<<code from first `case statement`>>> 
    goto end; 
label2: 
    <<<code from first `case statement`>>> 
    goto end; 
label3: 
    <<<code from first `case statement`>>> 
    goto end; 
default: 
    <<<code from `default` case>>> 
    goto end; 
end: 

Una de las razones de este método es más rápido se debe a que el código dentro de los condicionales es menor (por lo que hay una caché de instrucciones más pequeña sanción si el condicional está mal predicho). Además, el caso "fall-through" se vuelve más trivial de implementar (el compilador deja la declaración goto end).

Los compiladores pueden optimizar aún más la tabla de salto creando una matriz de punteros (en las ubicaciones marcadas por las etiquetas) y usar el valor que está activando como índice en esa matriz. Esto eliminaría casi todos los condicionales del código (excepto lo que fuera necesario para validar si el valor que está encendiendo coincide con uno de sus casos o no).

Una palabra de advertencia: las tablas de salto anidado son difíciles de generar y algunos compiladores se niegan incluso a intentar crear una. Por esa razón, evite anidar un switch dentro de otro switch si el código optimizado al máximo es importante para usted (no estoy 100% seguro de cómo MSVC en los identificadores particulares anidados switch es, pero el manual del compilador debería informarle).

+0

Una nota: no mencioné ningún número específico relacionado con el rendimiento del conmutador en relación con un árbol if-else porque los beneficios de la optimización dependen en gran medida del código. La única forma real de saber cuánto mejora obtiene de las optimizaciones es escribir el código en ambos sentidos y medir cuánto tiempo lleva ejecutar cada una. – bta

+13

El código dado es *** not *** a jump table. No representa ninguna tabla en absoluto para ese asunto. Sin embargo, el enlace a Wikipedia proporciona ejemplos correctos. – dualed