2011-04-18 8 views
13

Estamos en el proceso de migrar una aplicación a .NET 4.0 (desde 3.5). Uno de los problemas que estamos corriendo en sólo es reproducible en condiciones muy específicas:¿Puedo detener la ejecución de .NET 4 tail-call?

  • Sólo en un comunicado de construir
  • Sólo con la optimización activada y/o información de depuración se establece en AP-solamente.

Con esto quiero decir, si inhabilito la optimización y conjunto de información de depuración al completo, el problema desaparece.

El código en cuestión funciona bien en .NET 3.5, en modo de lanzamiento con la optimización, etc. habilitado, y lo ha hecho durante mucho tiempo.

Realmente no quiero sugerir que haya un error en el compilador de C#, entonces realmente mi pregunta es si hay alguna técnica que pueda usar para rastrear lo que podríamos estar haciendo mal para causar una optimización incorrecta.

Estoy en el proceso de intentar reducir este problema a un pequeño caso de prueba para que pueda publicar algún código aquí.

Editar:

He rastreado el problema a lo siguiente:

Tenemos este código en el constructor de un formulario:

public ConnectionForm() 
{ 
    LocalControlUtil.Configure("ConnectionForm", "Username", usernameLabel); 
    LocalControlUtil.Configure("ConnectionForm", "Password", passwordLabel); 
    LocalControlUtil.Configure("ConnectionForm", "Domain", domainLabel); 
    LocalControlUtil.Configure("ConnectionForm", "Cancel", cancelButton); 
    LocalControlUtil.Configure("ConnectionForm", "OK", okButton); 
} 

Estas llamadas son en cierta código de localización personalizado. El constructor para este formulario se llama desde otro ensamblado. El método LocalControlUtil.Configure llama al Assembly.GetCallingAssembly(), que devuelve el valor correcto para todas las llamadas anteriores, excepto la última.

Puedo reordenar las líneas de arriba, agregar nuevas o eliminar las actuales, y cada vez que es la última línea que no funciona.

Supongo que se trata de JIT que incluye la última llamada al método al lugar donde se llamó al constructor (en otro conjunto). Agregar [MethodImpl(MethodImplOptions.NoInlining)] al constructor anterior soluciona el problema.

¿Alguien sabe por qué sucede esto? Me parece extraño que la última línea solo pueda estar en línea. ¿Es este nuevo comportamiento en .NET 4.0?

Edición 2:

He reducido esto abajo ahora a una eliminación de llamada final, supongo causada por el new tail-call stuff in .NET 4.

En el código anterior, se elimina la última llamada a LocalControlUtil.Configure en el constructor y se pone en el método de llamada, que está en otro ensamblado. Como el método llama al Assembly.GetCallingAssembly, no recuperamos el ensamblaje correcto.

¿Hay alguna manera de evitar que el compilador (o el JIT o lo que sea que haga esto) elimine la llamada final?

+0

Vi una pregunta similar en SO el otro día y definitivamente parecía que había un error en el compilador ya que se estaba optimizando incorrectamente. Entonces * es * posible, pero según lo entiendo, es bastante raro. –

+0

Recuerdo haber leído sobre esto en un blog. De alguna manera, el compilador hace que ciertos objetos se eliminen demasiado pronto. El ejemplo fue un método de cronómetro que solo ejecutó 1 ciclo antes de fallar. Desafortunadamente no tengo un enlace a la publicación. – PiZzL3

+8

¿Puede darnos más detalles sobre el sabor del problema? Por ejemplo, hay muchas maneras en que los cálculos de punto flotante pueden diferir cuando se cambian las configuraciones de optimización. Existen numerosas formas en que los tiempos de subprocesos, los tiempos de recolección de basura, etc., pueden cambiar. ¿Cuál es la naturaleza del problema? –

Respuesta

0

No, no puedes.

.NET 4.0 optimiza más llamadas de cola que 3.5, que es una buena cosa. Nuestro código era loco, estúpido.

1

Creo que ya está en el camino correcto para encontrar el problema ... Reducirlo a la caja de prueba más pequeña es casi siempre un gran primer paso.

El siguiente será para comparar el IL que se genera para cada versión en algo como Reflector. La lectura de IL no es trivial (a menos que tenga una buena cantidad de experiencia), por lo que es fundamental minimizar la cantidad de código que debe revisar.

ACTUALIZACIÓN: Pensé en esto un poco más y si el problema no es discernible en el nivel IL, podría ser en el nivel JIT (Just In Time), que es mucho más difícil de depurar. Nunca tuve que trabajar en ese nivel, así que no tengo ninguna idea de esa parte del problema (si se trata de eso).

4

que acababa de poner esto en un comentario si no fuera demasiado largo, pero ¿ha intentado:

public ConnectionForm() 
{ 
    try 
    { 
    LocalControlUtil.Configure("ConnectionForm", "Username", usernameLabel); 
    LocalControlUtil.Configure("ConnectionForm", "Password", passwordLabel); 
    LocalControlUtil.Configure("ConnectionForm", "Domain", domainLabel); 
    LocalControlUtil.Configure("ConnectionForm", "Cancel", cancelButton); 
    LocalControlUtil.Configure("ConnectionForm", "OK", okButton); 
    } 
    catch 
    { 
    throw; 
    } 
} 

El hecho de que cualquier excepción que se plantea es lanzado hace que este más o menos un cambio nulo en términos de la código escrito, pero los límites de try-catch a menudo impiden las optimizaciones por parte del compilador y el jitter.

+0

Esto funciona, pero esto significa que necesito poner este try-catch en todas partes donde se llama 'LocalControlUtil.Configure' (muchos lugares). Realmente estoy esperando una solución global "No elimine las llamadas de cola". – jonnystoten

Cuestiones relacionadas