Es importante no confundir la declaración de cambio de C # con la instrucción de cambio de CIL.
El interruptor CIL es una tabla de salto, que requiere un índice en un conjunto de direcciones de salto.
Esto solo es útil si los casos del interruptor C # son adyacentes:
case 3: blah; break;
case 4: blah; break;
case 5: blah; break;
Pero de poca utilidad si no lo son:
case 10: blah; break;
case 200: blah; break;
case 3000: blah; break;
(Necesitaría una tabla de ~ 3000 entradas de tamaño, con solo 3 ranuras utilizadas)
Con expresiones no adyacentes, el compilador puede comenzar a realizar comprobaciones lineales if-else-if-else.
Con conjuntos de expresiones no adyacentes más grandes, el compilador puede comenzar con una búsqueda de árbol binario, y finalmente los últimos elementos if-else-if-else.
Con conjuntos de expresiones que contienen grupos de elementos adyacentes, el compilador puede buscar en árbol binario y finalmente un interruptor CIL.
Esto está lleno de "mays" y "mights", y depende del compilador (puede diferir con Mono o Rotor).
Repliqué sus resultados en mi máquina usando casos adyacentes:
tiempo total para ejecutar un interruptor de 10 vías, 10000 iteraciones (ms): 25.1383
tiempo aproximado por interruptor de 10 vías (ms): 0.00251383
tiempo total para ejecutar un conmutador de 50 vías, 10000 iteraciones (ms): 26.593
tiempo aproximado por conmutador de 50 vías (ms): 0.0026593
tiempo total para ejecutar un interruptor de 5000 vías, 10000 iteraciones (ms): 23.7094
tiempo aproximado por interruptor de 5000 vías (ms): 0.00237094
tiempo total para ejecutar un interruptor de 50000 vías, 10000 iteraciones (ms): 20.0933
tiempo aproximado por interruptor de 50000 vías (ms): 0.00200933
Luego también lo hice usando expresiones de casos no adyacentes:
tiempo total para ejecutar un conmutador de 10 vías, 10000 iteraciones (ms): 19.6189
tiempo aproximado por conmutador de 10 vías (ms): 0.00196189
tiempo total para ejecutar un interruptor de 500 vías, 10000 iteraciones (ms): 19.1664
tiempo aproximado por interruptor de 500 vías (ms): 0.00191664
tiempo total para ejecutar un conmutador de 5000 vías, 10000 iteraciones (ms): 19.5871
tiempo aproximado por conmutador de 5000 vías (ms): 0.00195871
Una declaración de cambio de caso no adyacente de 50,000 no se compilaría.
"Una expresión es demasiado larga o compleja para compilar cerca de 'ConsoleApplication1.Program.Main (string [])'
Lo curioso aquí es que la búsqueda de árbol binario parece un poco (probablemente no estadísticamente) más rápida que la instrucción de cambio de CIL.
Brian, has usado la palabra " constante ", que tiene un significado muy definido desde la perspectiva de la teoría de la complejidad computacional. Mientras que el ejemplo entero adyacente simplista puede producir CIL que se considera O (1) (constante), un ejemplo disperso es O (log n) (logarítmico), los ejemplos agrupados se encuentran en algún punto intermedio, y los ejemplos pequeños son O (n) (lineal )
Esto ni siquiera aborda la situación de String, en la que Generic.Dictionary<string,int32>
se puede crear una estática , y sufrirá una sobrecarga definitiva en el primer uso. El rendimiento aquí dependerá del rendimiento de Generic.Dictionary
.
Si marca la Especificación del lenguaje C # (no la especificación CIL), encontrará "15.7.2 La declaración de cambio" no menciona el "tiempo constante" o que la implementación subyacente incluso utiliza la instrucción de cambio CIL (tenga mucho cuidado de asumir tales cosas).
Al final del día, un cambio de C # contra una expresión entera en un sistema moderno es una operación de menos de un microsegundo, y normalmente no vale la pena preocuparse.
Por supuesto, estos tiempos dependerán de las máquinas y las condiciones. No prestaría atención a estas pruebas de tiempo, las duraciones de microsegundos de las que estamos hablando están eclipsadas por cualquier código "real" que se esté ejecutando (y debe incluir algún "código real", de lo contrario, el compilador optimizará la ramificación), o nerviosismo en el sistema. Mis respuestas se basan en el uso de IL DASM para examinar el CIL creado por el compilador de C #. Por supuesto, esto no es definitivo, ya que las instrucciones reales que ejecuta la CPU son creadas por el JIT.
Verifiqué las instrucciones finales de la CPU realmente ejecutadas en mi máquina x86, y puedo confirmar que un simple interruptor de configuración adyacente haga algo como:
jmp ds:300025F0[eax*4]
Donde una búsqueda de árbol binario está llena de:
cmp ebx, 79Eh
jg 3000352B
cmp ebx, 654h
jg 300032BB
…
cmp ebx, 0F82h
jz 30005EEE