40 votes

Est-ce que l'opérateur 'est' peut subir une optimisation en mode de lancement sur .NET 4?

Ci-dessous est un simple montage de test. Il réussit dans les versions de Débogage et échoue dans les versions Release (VS2010, .NET4 solution, x64):

[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;
    }
}

Il semble d'optimisation de code fait quelques ravages; si je le désactiver sur la Version de publication, il fonctionne aussi bien. C'était assez déroutant pour moi. Ci-dessous, j'ai utilisé ILDASM de démonter les 2 versions du build:

Debug 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 libération de l'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

Il semble un magasin et de la charge est optimisé loin. Le ciblage des versions antérieures de l' .NET framework est le problème de s'en aller, mais c'est peut être juste un coup de chance. Je trouve ce comportement un peu énervant, quelqu'un peut-il expliquer pourquoi le compilateur pense qu'il est prudent de faire une optimisation qui produit différents comportements observables?

Merci à l'avance.

20voto

Elian Ebbing Points 8363

Ce bug est déjà venu dans cette SORTE de question par Jacob Stanley. Jacob a déjà signalé le bug, et Microsoft a confirmé que c'est bien un bug dans le CLR JIT. Microsoft avait ceci à dire:

Ce bug sera corrigé dans une future version du moteur d'exécution. J'ai peur que c'est trop tôt pour dire si ce sera dans un service pack ou la prochaine version majeure.

Merci encore pour signaler le problème.

Vous devriez être en mesure de contourner le bug en ajoutant l'attribut suivant à l' TestChecker():

[MethodImpl(MethodImplOptions.NoInlining)]

15voto

Hans Passant Points 475940

Il n'est pas lié au compilateur C#, IL est identique. Vous avez trouvé un bug dans le .NET 4.0 gigue de l'optimiseur. Vous pouvez repro dans Visual Studio. Outils + Options, le Débogage, le Général, décochez l' "Supprimer JIT sur l'optimisation de charger le module" et exécutez la Version de construire afin de reproduire l'échec.

Je n'ai pas regardé d'assez près encore à identifier le bug. Il semble très étrange, il inlines la méthode et omet complètement le code de la boxe de conversion. Le code machine est sensiblement différent du code généré par la version 2 de la gigue.

Une propre solution de contournement n'est pas facile, vous pouvez le faire par la suppression de l'in-lining. Comme ceci:

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

Vous pouvez signaler le bug à connect.microsoft.com. Laissez-moi savoir si vous ne voulez pas et je vais prendre soin d'elle.


Jamais l'esprit, qui a été déjà fait. Il n'était pas résolu dans la version de maintenance qui a été inclus avec VS2010 SP1.


Ce bogue a été corrigé, je n'ai plus de repro il. Ma version actuelle de clrjit.dll est 4.0.30319.237 datée du 17 Mai 2011. Je ne peux pas dire exactement ce que la mise à jour est réparé. J'ai eu une mise à jour de sécurité sur Août 5th 2011 mis à jour clrjit.dll la révision 235 avec une date de Avr 12, que le plus tôt serait.

5voto

Talljoe Points 8423

Le magasin et la charge est essentiellement un nop autant que le contrôle du flux de passe, mais probablement massage certaines caches CPU, d'une certaine façon. Le débit réel juste les charges de l'argument sur la pile, vérifie si c'est une instance (qui retourne la valeur null ou de l'instance), il pousse nulle sur la pile, et compare (supérieur) ce qui résulte en une valeur de type boolean être laissé sur la pile.

Maintenant que la Gigue en fait est une autre histoire (et dépendrait de ce que la plateforme que vous utilisez. La Gigue va faire toutes sortes de choses folles, au nom de la performance (notre équipe a récemment obtenu des succès parce queue-appel d'optimisation changé d'optimiser à travers les limites d'un domaine qui a éclaté GetCallingAssembly()). Il est possible que la Gigue est inline IsDateTime, en remarquant qu'il n'y a pas moyen, il ne peut pas ne pas être un DateTime et juste pousser vrai sur la pile.

Il est aussi possible que votre version est de cibler un Cadre un peu différent, donc DateTime dans le montage d'essai n'est pas DateTime dans l'essai de l'assemblée.

Je me rends compte que ne pas répondre pourquoi votre code est la rupture.

3voto

sehe Points 123151

Pour référence, j'ai vérifié avec mono

  • Version 2.6.7 du compilateur JIT mono (Debian 2.6.7-3ubuntu1)
  • Version du compilateur JIT Mono 2.8.2 (voir / d1c74ad ven. Fév. 18 21:46:52 CET 2011)

Les deux ne présentaient aucun problème. Voici l'IL avec optimisation 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
 

Sans optimisations, c'est exactement pareil

Voici le résultat du code jitted de mono pour cet 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
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X