En un comentario en this answer (que sugiere el uso de operadores de cambio de bit sobre la multiplicación/división de enteros, para el rendimiento), pregunté si esto realmente sería más rápido. En el fondo de mi mente está la idea de que al algún nivel de, algo será lo suficientemente inteligente como para saber que >> 1
y / 2
son la misma operación. Sin embargo, ahora me pregunto si esto es cierto, y si lo es, a qué nivel ocurre.¿Hay alguna manera de ver el código nativo producido por theJITter para C#/CIL dado?
Un programa de prueba produce el siguiente CIL comparativo (con optimize
sucesivamente) por dos métodos que se dividen y cambian su argumento, respectivamente:
IL_0000: ldarg.0
IL_0001: ldc.i4.2
IL_0002: div
IL_0003: ret
} // end of method Program::Divider
frente
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: shr
IL_0003: ret
} // end of method Program::Shifter
Así que el compilador de C# está emitiendo div
o shr
instrucciones, sin ser inteligente. Ahora me gustaría ver el ensamblador x86 real que produce el JITter, pero no tengo idea de cómo hacer esto. ¿Es posible?
de edición para añadir
hallazgos
, gracias por respuestas, han aceptado la de nobugz porque contenía la información clave sobre esa opción depurador. Lo que eventualmente trabajado para mí es:
- interruptor para divulgar la configuración
- En
Tools | Options | Debugger
, apagar 'Suprimir optimización JIT en la carga del módulo' (es decir, queremos permitir la optimización JIT) - mismo lugar, apagamos 'Habilitar Sólo mi código' (es decir, queremos depurar todo código)
- Deja un comunicado
Debugger.Break()
algún lugar - construir el ensamblaje
- ejecutar el archivo .exe y cuando se rompe, depurar utilizando el vigente VS ejemplo
- Ahora la ventana de desmontaje que muestra el x86 real que va a ser ejecutado
Los resultados fueron esclarecedores para decir lo menos - resulta que JITter puede hacer aritmética. Aquí hay ejemplos editados de la ventana Desmontaje. Los diversos métodos -Shifter
se dividen por potencias de dos utilizando >>
; los diversos -Divider
métodos se dividen por números enteros usando /
Console.WriteLine(string.Format("
{0}
shift-divided by 2: {1}
divide-divided by 2: {2}",
60, TwoShifter(60), TwoDivider(60)));
00000026 mov dword ptr [edx+4],3Ch
...
0000003b mov dword ptr [edx+4],1Eh
...
00000057 mov dword ptr [esi+4],1Eh
Tanto estáticamente de división por 2 no sólo se han inline métodos, pero los cálculos reales se han hecho por el jitter
Console.WriteLine(string.Format("
{0}
divide-divided by 3: {1}",
60, ThreeDivider(60)));
00000085 mov dword ptr [esi+4],3Ch
...
000000a0 mov dword ptr [esi+4],14h
mismo con static-divide-por-3.
Console.WriteLine(string.Format("
{0}
shift-divided by 4: {1}
divide-divided by 4 {2}",
60, FourShifter(60), FourDivider(60)));
000000ce mov dword ptr [esi+4],3Ch
...
000000e3 mov dword ptr [edx+4],0Fh
...
000000ff mov dword ptr [esi+4],0Fh
Y statically-divide-by-4.
Lo mejor:
Console.WriteLine(string.Format("
{0}
n-divided by 2: {1}
n-divided by 3: {2}
n-divided by 4: {3}",
60, Divider(60, 2), Divider(60, 3), Divider(60, 4)));
0000013e mov dword ptr [esi+4],3Ch
...
0000015b mov dword ptr [esi+4],1Eh
...
0000017b mov dword ptr [esi+4],14h
...
0000019b mov dword ptr [edi+4],0Fh
Se inline y luego calcula todas estas divisiones estáticas!
Pero, ¿y si el resultado no es estático? Añadí al código para leer un número entero de la consola. Esto es lo que produce para las divisiones en que:
Console.WriteLine(string.Format("
{0}
shift-divided by 2: {1}
divide-divided by 2: {2}",
i, TwoShifter(i), TwoDivider(i)));
00000211 sar eax,1
...
00000230 sar eax,1
Así que a pesar de la CIL ser diferente, el jitter sabe que la división por 2 es el botón derecho del desplazamiento por 1.
Console.WriteLine(string.Format("
{0}
divide-divided by 3: {1}", i, ThreeDivider(i)));
00000283 idiv eax, ecx
Y sabe que hay que dividir a dividir por 3.
Console.WriteLine(string.Format("
{0}
shift-divided by 4: {1}
divide-divided by 4 {2}",
i, FourShifter(i), FourDivider(i)));
000002c5 sar eax,2
...
000002ec sar eax,2
Y kno ws que dividiendo por 4 es el botón derecho del desplazamiento por 2.
último (el mejor de nuevo!)
Console.WriteLine(string.Format("
{0}
n-divided by 2: {1}
n-divided by 3: {2}
n-divided by 4: {3}",
i, Divider(i, 2), Divider(i, 3), Divider(i, 4)));
00000345 sar eax,1
...
00000370 idiv eax,ecx
...
00000395 sar esi,2
Ha inline el método y trabajado a cabo la mejor manera de hacer las cosas, en base a la statically- argumentos disponibles Bonito.
Así que sí, en algún lugar de la pila entre C# y x86, algo es lo suficientemente inteligente para saber que >> 1
y / 2
son los mismos. Y todo esto me ha dado más peso en mi mente a mi opinión de que al sumar el compilador C#, el JITter y el CLR hace un mucho más claro que cualquier pequeño truco que podamos probar como humildes programadores de aplicaciones :)
¿Puedes publicar tus hallazgos por el bien de todos nosotros? gracias :) – flesh