2011-04-04 10 views
40

A continuación se muestra un accesorio de prueba simple. Tiene éxito en versiones de depuración y no en las versiones de lanzamiento (VS2010, .NET4 solución, x64):¿Puede el operador C# 'es' sufrir en optimización de modo de lanzamiento en .NET 4?

[TestFixture] 
public sealed class Test 
{ 
    [Test] 
    public void TestChecker() 
    { 
     var checker = new Checker(); 
     Assert.That(checker.IsDateTime(DateTime.Now), Is.True); 
    } 
} 

public class Checker 
{ 
    public bool IsDateTime(object o) 
    { 
     return o is DateTime; 
    } 
} 

Parece optimización de código da rienda suelta a algunos estragos; si lo desactivo en la compilación Release, también funciona. Eso fue bastante desconcertante para mí. A continuación, he utilizado ILDASM desmontar las 2 versiones de la construcción:

depuración IL:

.method public hidebysig instance bool IsDateTime(object o) cil managed 
{ 
    // Code size  15 (0xf) 
    .maxstack 2 
    .locals init (bool V_0) 
    IL_0000: nop 
    IL_0001: ldarg.1 
    IL_0002: isinst  [mscorlib]System.DateTime 
    IL_0007: ldnull 
    IL_0008: cgt.un 
    IL_000a: stloc.0 
    IL_000b: br.s  IL_000d 
    IL_000d: ldloc.0 
    IL_000e: ret 
} // end of method Validator::IsValid 

la liberación de IL:

.method public hidebysig instance bool IsDateTime(object o) cil managed 
{ 
    // Code size  10 (0xa) 
    .maxstack 8 
    IL_0000: ldarg.1 
    IL_0001: isinst  [mscorlib]System.DateTime 
    IL_0006: ldnull 
    IL_0007: cgt.un 
    IL_0009: ret 
} // end of method Validator::IsValid 

Parece una tienda y la carga se optimiza de distancia. Apuntar a versiones anteriores del framework .NET hizo que el problema desapareciera, pero eso podría ser un golpe de suerte. Encontré este comportamiento algo desconcertante, ¿alguien puede explicar por qué el compilador pensaría que es seguro realizar una optimización que produzca un comportamiento diferente?

Gracias de antemano.

+0

Donde hizo el El método 'IsValid' viene de? No estás mostrando eso en el código C#. –

+0

Lo siento, he cambiado el nombre del método después de que se realizó el volcado de ILDASM; nombrando arreglado ahora. – Cumbayah

+0

me huele a errores ... muy agradable de encontrar – sehe

Respuesta

20

Este error ya apareció en this SO question por Jacob Stanley. Jacob ya tiene reported the bug, y Microsoft ha confirmado que de hecho es un error en el CLR JIT. Microsoft tuvo esto que decir:

Este error se solucionará en una versión futura del tiempo de ejecución. Me temo que es demasiado pronto para decir si estará en un paquete de servicio o en la próxima versión principal.

Gracias de nuevo por informarnos del problema.

Usted debe ser capaz de evitar el error al añadir el siguiente atributo a TestChecker():

[MethodImpl(MethodImplOptions.NoInlining)] 
15

No está relacionado con el compilador C#, el IL es idéntico. Encontraste un error en el optimizador de jitter de .NET 4.0. Puede reproducirlo en Visual Studio. Herramientas + Opciones, Depuración, General, desmarque la opción "Suprimir optimización de JIT en la carga del módulo" y ejecute la versión Liberar para reprogramar el error.

No lo he examinado lo suficiente para identificar el error. Parece muy extraño, enmarca el método y omite por completo el código para la conversión de boxeo. El código de la máquina es sustancialmente diferente del código generado por la versión 2 jitter.

Una solución limpia no es tan fácil, puede hacerlo suprimiendo la alineación. De esta manera:

[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] 
    public bool IsDateTime(object o) { 
     return o is DateTime; 
    } 

Puede informar el error en connect.microsoft.com. Avíseme si no quiere y me ocuparé de ello.


No importa, eso fue already done. No se corrigió en la versión de mantenimiento incluida con VS2010 SP1.


Este error ha sido reparado, ya no puedo reproducirlo. Mi versión actual de clrjit.dll es 4.0.30319.237 con fecha del 17 de mayo de 2011. No puedo decir exactamente qué actualización lo reparó. Recibí una actualización de seguridad el 5 de agosto de 2011 que actualizaba clrjit.dll a la revisión 235 con una fecha del 12 de abril, que sería la más temprana.

+0

Vaya, también se cubre aquí: http://stackoverflow.com/questions/2819102/c-is-type-check-on-struct-odd-net-4-0-x86-optimization-behavior/2821075#2821075 –

5

El almacenamiento y la carga es esencialmente un nop en lo que respecta al control de flujo, pero probablemente realice un masaje de algunos cachés de CPU de alguna manera. El flujo real simplemente carga el argumento en la pila, verifica si es una instancia (que devuelve nulo o la instancia), empuja nulo a la pila y compara (mayor que) lo que da como resultado que se deje un booleano en la pila.

Ahora, lo que el JITter hace con él es otra historia (y dependerá de la plataforma que esté utilizando. JITter hará todo tipo de cosas locas en nombre del rendimiento (nuestro equipo recibió recientemente un golpe porque la optimización cambió para optimizar los límites de dominio que rompieron GetCallingAssembly()). Es posible que JITter esté publicando IsDateTime, advirtiendo que no hay forma de que no pueda ser un DateTime y simplemente haga true en la pila.

También es posible que su versión de lanzamiento tenga como objetivo un Framework ligeramente diferente, por lo que DateTime en el ensamblaje de prueba no es DateTime en el ensamblaje probado.

Me doy cuenta de que no responde por qué su código se está rompiendo.

+0

+ 1 para aclarar que la tienda y la carga son semánticamente un NOP, pero pueden tener una influencia indirecta a través de los efectos secundarios de la caché. – Cumbayah

+0

Bonita anécdota sobre GetCallingAssembly(). –

3

Como referencia he comprobado con mono

  • Mono versión 2.6.7 compilador JIT (Debian 2.6.7-3ubuntu1)
  • compilador Mono JIT versión 2.8.2 (sehe/d1c74ad vie 18 de feb. 21:46:52 CET 2011)

Ambos no presentaron problemas de ningún tipo. Aquí está el IL con optimización en 2.8.2

.method public hidebysig 
     instance default bool IsDateTime (object o) cil managed 
{ 
    // Method begins at RVA 0x2130 
    // Code size 10 (0xa) 
    .maxstack 8 
    IL_0000: ldarg.1 
    IL_0001: isinst [mscorlib]System.DateTime 
    IL_0006: ldnull 
    IL_0007: cgt.un 
    IL_0009: ret 
} // end of method Checker::IsDateTime 

Sin optimizaciones se exactamente la misma

aquí es el resultado de código compilados JIT de Mono para este IL:

00000130 <TestData_Checker_IsDateTime_object>: 
    130:  55      push %ebp 
    131:  8b ec     mov %esp,%ebp 
    133:  53      push %ebx 
    134:  56      push %esi 
    135:  83 ec 10    sub $0x10,%esp 
    138:  e8 00 00 00 00   call 13d <TestData_Checker_IsDateTime_object+0xd> 
    13d:  5b      pop %ebx 
    13e:  81 c3 03 00 00 00  add $0x3,%ebx 
    144:  8b 45 0c    mov 0xc(%ebp),%eax 
    147:  89 45 f4    mov %eax,-0xc(%ebp) 
    14a:  8b 75 0c    mov 0xc(%ebp),%esi 
    14d:  83 7d 0c 00    cmpl $0x0,0xc(%ebp) 
    151:  74 1a     je  16d <TestData_Checker_IsDateTime_object+0x3d> 
    153:  8b 45 f4    mov -0xc(%ebp),%eax 
    156:  8b 00     mov (%eax),%eax 
    158:  8b 00     mov (%eax),%eax 
    15a:  8b 40 08    mov 0x8(%eax),%eax 
    15d:  8b 48 08    mov 0x8(%eax),%ecx 
    160:  8b 93 10 00 00 00  mov 0x10(%ebx),%edx 
    166:  33 c0     xor %eax,%eax 
    168:  3b ca     cmp %edx,%ecx 
    16a:  0f 45 f0    cmovne %eax,%esi 
    16d:  85 f6     test %esi,%esi 
    16f:  0f 97 c0    seta %al 
    172:  0f b6 c0    movzbl %al,%eax 
    175:  8d 65 f8    lea -0x8(%ebp),%esp 
    178:  5e      pop %esi 
    179:  5b      pop %ebx 
    17a:  c9      leave 
    17b:  c3      ret  
    17c:  8d 74 26 00    lea 0x0(%esi,%eiz,1),%esi 
+0

Gracias por sus comentarios, que respalda las otras respuestas que el error no es, de hecho, en la IL generada, pero en una etapa posterior, en el JIT. – Cumbayah

+0

Desarmado de código JITTED agregado por si acaso :) – sehe

Cuestiones relacionadas