Peu importe le langage du moment qu’on ai l’ivresse

Il y a de cela quelques années, j’ai développé un outil, qui permettait de comparer que le code MSIL (Microsoft Intermediate langage) VB, C# et C++/CLI était identique, afin de démontrer qu’entre les principaux langages .NET, il n’y avait pas de différence notable puisque le code MSIL étant ensuite compilé à la volée par le JIT Compilateur du CLR (Common Language Runtime).

Néanmoins en comparant avec cet outil le code MSIL généré, j’ai était surpris de plusieurs petits détails.

Partons du code simple suivant ou j’instancie deux variables de type entier x et y à 100, puis que j’additionne (x+y).

VB

Code Snippet

  1. Public Function MethodVB(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer
  2.         Dim x As Integer = 100
  3.         Dim y As Integer = 100
  4.         Return x + y
  5.     End Function

Code MSIL généré par le compilateur VB

image

C#

Code Snippet

  1. public int  MethodCS(int a,int b, int c)
  2.     {
  3.         int y = 100;
  4.         int x = 100;
  5.         return y + x;
  6.     }

Code MSIL généré par le compilateur C#

image

C++/CLI

Code Snippet

  1. int TestClassCplus::Class1::MethodCplus(
  2.         int a, int b,int c)
  3.     {        
  4.         int y=100;
  5.         int x=100;           
  6.         return y+x;                                 
  7.     }

Code MSIL généré par le compilateur C++/CLI

image

La première constatation que l’on peut faire, c’est que le code MSIL émit est différent entre les 3 langages.

On peut s’apercevoir qu’à la ligne 8, l’instruction ADD est différente entre VB et C#.

Cela s’explique très simplement. En effet VB utilise l’instruction ADD.OVF par défaut, c’est à dire qu’il vérifie tous les dépassements de capacité (et ceci pour toutes les autres opérations), ce que ne fait pas C#. Il est possible de désactiver cette option en cochant la bonne case dans les options avancées du compilateur VB .
image

Pour obtenir peu ou prou le même code MSIL entre ces deux langages, reportez-vous à l’article que j’ai écrit à ce sujet en 2005 Visual Basic 2005 et les performances

Par contre, on peut s’apercevoir qu’avec C++/CLI c’est une toute autre affaire.

En effet, le compilateur C++/CLI, n’émet pas l’instruction MSIL ADD, mais comme il voit que ce sont deux constantes à additionner, lors de la compilation, il les additionne tout simplement et charge la constante 200 .

Pour corser l’affaire, nous n’allons plus faire appel à des constantes mais à des variables passées en paramètre à une méthode

VB

Code Snippet

  1. Public Function MethodVB(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer        
  2.       Return Carre(a)
  3.   End Function
  4.  
  5.   Function Carre(ByVal a As Integer) As Integer
  6.       Return a * a
  7.   End Function

Code MSIL généré par le compilateur VBimage

C#

Code Snippet

  1. public intMethodCS(int a,int b, int c)
  2.       {
  3.           return Carre(a);
  4.       }
  5.       public int Carre(int a)
  6.       {
  7.           return a * a;
  8.       }

Code MSIL généré par le compilateur C#image

C++/CLI

Code Snippet

  1. int TestClassCplus::Class1::MethodCplus(
  2.         int a, int b,int c)
  3.     {                
  4.         return Carre(a);                         
  5.     }
  6. int TestClassCplus::Class1::Carre(int a)
  7.         {
  8.             return a * a;
  9.         }

Code MSIl généré par le compilateur C++/CLI

image

Une fois de plus il y a des différences.

C# et VB font bien appel à la méthode Carre (instruction CALL) a la différence prêt que C# reconnait automatiquement que la méthode Carre est une méthode de l’instance courante de la classe, alors que VB fait un appel à la table virtuelle des méthodes (CALLVIRT) sur de de très nombreux appels, ceci peut quand même avoir une légère incidence sur les performances. Si on souhaite que VB se comporte comme C#, il suffit de rajouter le mot clé MyClass devant l’appel de la méthode, comme indiqué sur le bout de code suivant.

Code Snippet

  1. Public Function MethodVB(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer        
  2.      Return MyClass.Carre(a)
  3.  End Function

Concernant C++/CLI, c’est encore une toute autre affaire. En effet, C++ n’émet pas d’appel du tout à la méthode carre(), évitant ainsi un appel et retour de méthode qui peut être couteux. En effet il s’aperçoit que la méthode carre, ne fait qu’une multiplication de la variable a passée en paramètre. Le compilateur décide alors de la dupliquer (DUP), puis de la multiplier(MUP).

Un dernier exemple pour la route on test ici qu’une condition de retour d’une méthode soit vraie. Si tel est le cas, on imprime le texte “print”.

VB

Code Snippet

  1.     Public Function MethodVB(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer
  2.     If (MyClass.z()) Then
  3.         Console.WriteLine("print")
  4.     End If
  5.     Return 0
  6. End Function
  7. Function z() As Boolean
  8.     Return True
  9. End Function

image

C#

Code Snippet

  1. public intMethodCS(int a,int b, int c)
  2.       {
  3.           if (z())
  4.           {
  5.               Console.WriteLine("print");
  6.           }
  7.           return 0;
  8.       }
  9.       public bool z()
  10.       {
  11.           return true;
  12.       }

image

C++/CLI

Code Snippet

  1. int TestClassCplus::Class1::MethodCplus(
  2.         int a, int b,int c)
  3.     {                
  4.          if (z())
  5.             {
  6.                 Console::WriteLine("print");
  7.             }
  8.             return 0;
  9.     }
  10.     bool z()
  11.         {
  12.             return zz();
  13.         }
  14.         bool zz()
  15.         {
  16.             return zzz();
  17.         }
  18.         bool zzz()
  19.         {
  20.  
  21.             return zzzz();
  22.         }
  23.         bool zzzz()
  24.         {
  25.  
  26.             return zzzzz();
  27.         }
  28.         bool zzzzz()
  29.         {
  30.  
  31.             return zzzzzz();
  32.         }
  33.         bool zzzzzz()
  34.         {
  35.  
  36.             return true;
  37.         }

image

Pour VB et C# les compilateurs émettent l’instruction de branchement (BRFALSE) vers la ligne 12 si la condition est fausse, si elle est vraie, les instructions suivantes sont exécutées, chargement de la constante chaine de caractères (LDSTR) puis appel (CALL) à la méthode statique WriteLine.

Pour C++/CLI, nous avons un peu corsé l’affaire, car la méthode qui retourne TRUE, est définie à la fin de plusieurs appels successif, comme vous pouvez le voir sur le code ci-dessus. De la même manière que les exemples précédant, le compilateur optimise le code, car il s’aperçoit que la méthode retournera toujours TRUE, donc, qu’il n’est pas utile de mettre en place le branchement conditionnel.

Il existe comme cela encore certains exemples, ou le compilateur C++/CLI, est semble t-il plus efficace. Mais attention, ne me faite pas dire ce que je n’ai pas dis. Car ce sont des exemples très simple. Mais il me semblait intéressant de le faire remarquer.

D’ailleurs, C++/CLI n’est intéressant, à mon humble avis, que dans l’optique ou vous souhaitez faire de l’intéropérabilité avec du code natif (ISO C++ par exemple).

Mais ce que l’on peut tirer de ces exemples, c’est qu’avec un peu de bonne volonté il est possible de faire du code VB aussi performant que du code C#. L’utilisation de l’un ou de l’autre est à votre convenance, et c’est ce que vous en faite qui est important, et non pas le langage que vous utilisez.

Peu importe le langage, pourvu qu’on ai l’ivresse, et qu’on soit fier d’être développeur !!

Eric Vernié