2009-12-22 14 views
19

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

+0

¿Puedes publicar tus hallazgos por el bien de todos nosotros? gracias :) – flesh

Respuesta

8

No obtendrá resultados significativos hasta que configure el depurador. Herramientas + Opciones, Depuración, General, apague "Suprimir la optimización de JIT en la carga del módulo". Cambia a la configuración del modo de lanzamiento. Un fragmento de la muestra:

static void Main(string[] args) { 
    int value = 4; 
    int result = divideby2(value); 
} 

Usted lo está haciendo bien si el desmontaje se ve así:

00000000 ret 

Usted tendrá que engañar al optimizador JIT para forzar la expresión a evaluar. Usando la consolaWriteLine (variable) puede ayudar. Entonces debería ver algo como esto:

0000000a mov   edx,2 
0000000f mov   eax,dword ptr [ecx] 
00000011 call  dword ptr [eax+000000BCh] 

Sí, evaluó el resultado en tiempo de compilación. Funciona bastante bien, ¿no?

+0

¿Por qué no obtendrá resultados significativos cuando esté activada la opción "Suprimir la optimización de JIT en la carga del módulo"? Mi interpretación es que habilitar esto solo significa que debes depurar el código nativo optimizado/"versión". – Justin

3

Sí. Visual Studio tiene un desensamblador incorporado para hacer eso. Sin embargo, debes agregar el comando a tu barra de menú. Vaya a Extras/Customize/Commands (no sé si realmente se llaman así en la versión en inglés) y agregue el comando Dissassembly, que está uniendo Debugging, en algún lugar de la barra de menú.

Luego, establezca un punto de interrupción en su programa y cuando se rompa, haga clic en este comando de Desarmado. VS le mostrará el código de máquina desmontada.

Ejemplo de salida de un divisor método:

public static int Divider(int intArg) 
    { 
00000000 push  ebp 
00000001 mov   ebp,esp 
00000003 push  edi 
00000004 push  esi 
00000005 push  ebx 
00000006 sub   esp,34h 
00000009 mov   esi,ecx 
0000000b lea   edi,[ebp-38h] 
0000000e mov   ecx,0Bh 
00000013 xor   eax,eax 
00000015 rep stos dword ptr es:[edi] 
00000017 mov   ecx,esi 
00000019 xor   eax,eax 
0000001b mov   dword ptr [ebp-1Ch],eax 
0000001e mov   dword ptr [ebp-3Ch],ecx 
00000021 cmp   dword ptr ds:[00469240h],0 
00000028 je   0000002F 
0000002a call  6BA09D91 
0000002f xor   edx,edx 
00000031 mov   dword ptr [ebp-40h],edx 
00000034 nop    
    return intArg/2; 
00000035 mov   eax,dword ptr [ebp-3Ch] 
00000038 sar   eax,1 
0000003a jns   0000003F 
0000003c adc   eax,0 
0000003f mov   dword ptr [ebp-40h],eax 
00000042 nop    
00000043 jmp   00000045 
    } 
+2

No hay necesidad de agregar ese comando a VS, está en el menú Depuración-> Windows. –

2

mientras estás depuración (y sólo mientras se está depuración) basta con hacer clic en Depurar - Windows - Desmontaje o pulse el correspondiente acceso directo Ctrl + Alt + RE.

Cuestiones relacionadas