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
- Public Function MethodVB(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer
- Dim x As Integer = 100
- Dim y As Integer = 100
- Return x + y
- End Function
Code MSIL généré par le compilateur VB
C#
Code Snippet
- public int MethodCS(int a,int b, int c)
- {
- int y = 100;
- int x = 100;
- return y + x;
- }
Code MSIL généré par le compilateur C#
C++/CLI
Code Snippet
- int TestClassCplus::Class1::MethodCplus(
- int a, int b,int c)
- {
- int y=100;
- int x=100;
- return y+x;
- }
Code MSIL généré par le compilateur C++/CLI
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 .
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
- Public Function MethodVB(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer
- Return Carre(a)
- End Function
- Function Carre(ByVal a As Integer) As Integer
- Return a * a
- End Function
Code MSIL généré par le compilateur VB
C#
Code Snippet
- public intMethodCS(int a,int b, int c)
- {
- return Carre(a);
- }
- public int Carre(int a)
- {
- return a * a;
- }
Code MSIL généré par le compilateur C#
C++/CLI
Code Snippet
- int TestClassCplus::Class1::MethodCplus(
- int a, int b,int c)
- {
- return Carre(a);
- }
- int TestClassCplus::Class1::Carre(int a)
- {
- return a * a;
- }
Code MSIl généré par le compilateur C++/CLI
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
- Public Function MethodVB(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer
- Return MyClass.Carre(a)
- 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
- Public Function MethodVB(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer
- If (MyClass.z()) Then
- Console.WriteLine("print")
- End If
- Return 0
- End Function
- Function z() As Boolean
- Return True
- End Function
C#
Code Snippet
- public intMethodCS(int a,int b, int c)
- {
- if (z())
- {
- Console.WriteLine("print");
- }
- return 0;
- }
- public bool z()
- {
- return true;
- }
C++/CLI
Code Snippet
- int TestClassCplus::Class1::MethodCplus(
- int a, int b,int c)
- {
- if (z())
- {
- Console::WriteLine("print");
- }
- return 0;
- }
- bool z()
- {
- return zz();
- }
- bool zz()
- {
- return zzz();
- }
- bool zzz()
- {
- return zzzz();
- }
- bool zzzz()
- {
- return zzzzz();
- }
- bool zzzzz()
- {
- return zzzzzz();
- }
- bool zzzzzz()
- {
- return true;
- }
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é
Comments
Anonymous
April 20, 2012
Bonjour et merci de ton article. Pourquoi les compilateurs VB et C# sont moins intelligent que celui du C++ ? :D Question sérieuse : Penses tu que sur de gros projet, la différence peut-être flagrante ? Merci.Anonymous
May 22, 2012
Bonjour Said, Avec un peu de retard je réponds, C++ profite de plusieurs années d'experience que n'ont pas VB.NEt et C#, mais de toute façon, c'est le Just In time compiler qui fera le boulot plutot que le compiler d'IL lui même. Sur un gros projet à mon avis cela ne dépend pas du langage forcement, mais du développeur. Néanmoins, si la performance est nécessaire dans certain cas et qu'on ne peut l'obtenir avec des langages de Haut niveau comme C# et ou VB, des scénarios hybrides sont toujours possibles sans pour cela remettre en question le choix .NET Eric