Tutoriel optimisation avancées : compilation, compilateur et apis

Contenu du snippet

Utiliser un programme en l'exécutant sous vb6, c'est bien.
Utiliser un programme compilé en .exe, c'est mieux!

Pourquoi ? La réponse rapide est : car les performances ne sont pas les mêmes!
Dans ce tutorial vous sera exposé les différentes compilations possible pour optimiser les performances de vos routines vb.

nota : ide = interface de développement, aka "dans vb"
sources : msdn, tests perso.

1/ L'IDE (spéculations) :

Lorsque on exécute un programme sous l'ide de vb, celui-ci interprète ligne par ligne (bloc par bloc) le comportement de l'application en faisant une analyse de la syntaxe. Chaque méthode, chaque fonctions, chaque instructions écrite appel dans msvbvm60.dll une "api" qui exécutera l'instruction lorsque le pointeur d'exécution (la "ligne jaune"...) sera dessus.
Rien de plus normal, pensez-vous ? sachez qu'un tel comportement existe sous internet explorer lorsque il exécute du vbscript ou du javascript! Que faut-il conclure ? que le langage visual basic serai qu'un langage de scripting ??? Voyez immédiatement que tout les problèmes de perfomances que l'on rencontre sous vb vient d'être expliquer dans cette phrase!
Juger de la performance d'une routine en l'exécutant sous l'ide est donc une grosse erreur! (erreur que j'ai commise également, rassurez-vous, c'est pour ça que j'écrit ce tutorial :p !).
La petite "géguerre" qui oppose les routines écrite en langage C/C++ au routines VB, comme quoi le "C" est toujours plus rapide que "VB", etc... tout s'explique! comment voulez-vous qu'un .vbs "optimisé" gagne contre de l'asm compilé? Et oui, n'oublions pas de les développeurs en langage C/C++ n'on pas une IDE comme VB : pour tester leur programme, ils compilent toujours!

Rappelez-vous maintenant : avec l'arrivée de vb5, microsoft vantait le fait que le compilateur créait un .exe en "code natif", c'est-à-dire : de l'asm compilé! et oui, identique au C/C++.
Donc plus d'hésitations! Lorsque votre programme est débuggé, compilez-le et utilisez-le compilé ainsi!

2/ LE COMPILATEUR :

Bien. Attaquons-nous à l'étude du compilateur fourni avec vb6 (p-e valable avec vb5).
Ouvrez une source sous vb...
Allez dans le Menu Fichier > créer Projet1.exe puis bouton "Options...", onglet "Compilation"
Vous avez alors accès aux différentes options de compilations

Compilation P-Code :
Les routines du programme sont transcrite en p-code (recherchez sur le net des infos sur le p-code). La compilation en p-code oblige l'utilisation d'un interpreteur de p-code puisqu'il ne s'agit pas d'asm compilé. Vous n'obitendrez que peu de performances en utilisant cette méthode de compilation. En revanche, le p-code autorise des artefacts de programmation qui ne sont pas autorisé via la compilation en code natif, ce qui explique qu'un programme compilé en p-code est plus "stable".
Notez que le p-code peut être décompilé facilement.

Compilation en code natif :
Les routines du programme sont interprété puis compilé en binaire (asm compilé). Les performances des algoritmes sont donc maximale car il n'y a plus d'interprétation.
Attention, il s'agit des "performances maximales" pour des routines compilé sous vb. Comme dit précédemment, les routines sont interprété puis compilé définitivement. Le compilateur "code natif" permet d'agir sur la méthode d'interprétation ainsi que sur la méthode de compilation :

- optimiser la rapidité du code :
l'interpréteur du compilateur simplifiera les structures vers un code commun "pré-optimisé" (par exemple, un "for... next" ou un "do... loop until" sera équivalent). Il y aura également une étude des algorithmes simple pour les "résumer" (par exemple : "do: i=i+1:loop while i < 1000" sera remplaçé par "i = 1000" ...). Le code finale sera donc potentiellement plus rapide car l'interprétation aura simplifier le code. Notez donc qu'il est possible de "manuellement" optimiser la rapidité du code, en programmant de la façon la plus simple qu'il soit, les routines classique (A ce sujet, voir mon précédent "tutoriel optimisation avancées").

- optimiser la taille du code :
l'interpréteur du compilateur réduira la taille des structures pour obtenir un code binaire plus petit (mais pas obligatoirement le plus adapté). Il en résulte un .exe plus petit que s'il n'y a pas d'optimisation, mais parfois moins performant. La taille finale de l'exe sera entre 5% et 15% plus petit que sans optimisation (en arrondissant au multiple de 4096 octets, le gain est parfois de 0%). Il est difficile de réduire la taille du .exe compilé en réduisant manuellement la taille des algorithmes sous l'ide (sauf peut-être en supprimant tout ce qui est inutile : subs() jamais appelé, objets/classes inutile, etc...). Il existe un moyen plus astucieux pour réduire la taille du code compilé que d'utiliser cette option... (voir "optimisations avancées...").

- pas d'optimisation :
l'interpréteur traduit "tout ce qu'il lit" en binaire sans restructurations particulière. De toutes évidence ce mode n'est pas interssant... sauf pour une comparaisons avec les autres modes su-cités!

Viennent ensuite deux options en complément :

- Favoriser le Pentium Pro :
la compilation utilisera préférentiellement les instructions ASM fonctionnant rapidement dans les core Pentium Pro. On peut s'attendre a quelques performances supérieurs sur les "vieux" pentiums ou pentiums-2 et a des performances moindres sur les k6/cyrix/486... Mais sur les processeur tel que PIII, PIV, Celeron ou K7, il n'y aura pas de différences.

- Générer des informations de débogage symbolique :
équivalent de la compilation "debug" de vc++, cela permet le débogage du programme compilé en utilisant des débogueur compatible CodeView (le débogueur de VC++ par exemple...). Cela interessera ceux qui ont l'habitude déboguer un programme compilé (les codeurs c/c++ ...). Malheureusement je ne sais pas quels débogages supplémentaires sont accessible via cette méthode, par rapport a ce qui est possible sous l'ide.

Cliquez à présent sur le bouton intitulé "Optimisations avancées..." :
vous avez accès à des options supplémentaires. Aussi innocentes soit-elles, sachez que en cochant judicieusement les cases là où c'est nécessaire, vous pouvez gagner instantanément 30% de vitesse d'exécution, EN PLUS de l'option "Optimiser la rapidité du code" ! Mais attention, vous pouvez aussi réduire les performances, voir même provoquer des erreurs non gérées par "On Error...".
Pour les performances optimales, vous ne devrez cochez que les cases nécessaire. En lisant la description ci-dessous, identifiez si vos routines seront optimisé ou non via l'option choisi.

- Pas d'utilisation d'alias :
optimise le stockage des variables en mémoire, lorsque les références sont commune. Lorsque vous utilisez des arguments ByRef pour appeler des subs(), la compilation binaire plaçera astucieusement en cache les variables régulièrement appelé, comme c'est le cas dans les boucles (do: call Toto(i): i=i+1: loop until i > 1000 ; avec Sub Toto(i as long)). Cette optimisation est interessante si votre code appelle SOUVENT les mêmes variables partagés dans beaucoup de sous-fonctions. Dans le cas contraire (le plus probable), la "mise en cache" forcé des variables seront des intructions superflue et vous risquez de perdre des performances.

- Supprimer les contrôles de limites de tableaux :
prenons un exemple : "dim tabl(1 to 1000) as long: for i = 1 to 1000: tabl(i) = i*2: Next". Lorsque le code est compilé, avant l'instruction "tabl(i) =" il y a un test pour savoir si "i > 1000" et si c'est le cas, le programme appelle __vbaGenerateBoundsError, générant une erreur pouvant être intercepté via "On Error...", pour éviter le "seg fault" (tentative d'accès à une zone mémoire non alloué/autorisé). Bref, si on ne coche pas cette case, cela reviendrai a faire un "If i > UBound(tabl()) Then Err.Raise" avant chaque appel dans un tableau!! (collection d'objets inclus). En conclusion, si votre programme utilise un tableau, et si vous ne risquez pas de "dépasser" ce tableau, cochez cette case! vous y gagnerez assurément en performances!

- Supprimer les contrôles de dépassement sur les entiers :
à chaque calcul aboutissant dans un entier, il y a une étape pour vérifier si l'entier ne dépasse pas la valeur maximale autorisé dans son type (+32767 dans le cas du Integer). S'il y a dépassement, le programme génère une erreur pouvant être intercepté par "On Error..." . A l'instar de l'option précédente, en cochant cette case vous gagnerez immédiatement en performances puisque il y aura moins de tests. En revanche, si votre programme risque de faire un dépassement, attendez-vous à une inversion de signe dans les résultats! exemple "dim i as integer: i = 32767 + 1" l'ide génère une erreur, tandis que le code compilé admettra "i = -1" .

- Supprimer les contrôles d'erreurs en virgule flottante :
équivalent de l'option précédente, mais sur les type Single et Double. Si une opération arithmétique dépasse la limite autorisé pour un type de données, le compilateur appelle le générateur d'erreur __vbaErrorOverflow, pouvant être intercepté par "On Error..." . Cochez cette case si vous êtes sûre de ne pas faire un tel dépassement pour supprimer les quelques instructions de tests, et ainsi gagner en performances. S'il y a une erreur de ce type durant l'exécution alors que le programme a été compilé avec cette option, le résultat du calcul sera faussé (on a pas accès au registre EFLAGS en vb :-/ le bit 11 de ce registre est plaçé sur "1" lorsqu'il y a un dépassement).

- Autoriser les opérations en virgule flottante non arrondies :
Si vous manipuler des chiffres a virgules flottantes dans les opérations arithmétique (type Single ou Double), par défaut vb ajuste en permanence le type pour qu'il y ai correspondance dans, par exemple, les comparaisons (dim s as single, d as double:...: if s = d then ; dans cette exemple, d aura été converti en type single pour réaliser la comparaison). En cochant cette case, vous supprimez cet ajustement automatique. Cette option est interessante si vous travaillez avec la même précision tout au long de vos routine (uniquement Single ou Double) : en effet la compilation binaire utilisera plus efficacement les registres mémoires et n'inserera pas d'instruction d'ajustement des "virgules". Bref, un gain de vitesse, mais une opération tel que "dim s as Single, d as Double : if s = d Then" sera automatiquement faux! il faudra écrire "If s = CSng(d) Then" ... bref, ajuster manuellement la préçision.

- Supprimer les contrôles Safe Pentium FDIV :
une génération de pentium (ceux compris entre 75MHz et 133Mhz) renvoyais un résultat légèrement faux lors de la division de deux chiffre a virgule flottante (instruction asm FDIV). Pour palier le problème, le compilateur intègre un correcteur automatique après chaque division en virugle flottante dans le code. Si votre programme réalise des divisions de ce type et que vous n'avez pas l'intention de l'utiliser sur un "vieux" pentium, cochez cette case pour supprimer ces tests et vous gagnerez quelques peux en performances.

En conclusion, n'oubliez pas :
a) Jugez de la vitesse de vos programme lorsqu'ils sont compilé pour les comparer aux autres!
b) Pensez aux optimisations avancés pour la compilation
c) code natif + rapidité du code + suppression des contrôles devrai être toujours les options coché de votre compilateur !

3/ REMARQUE RELATIVE AUX APIS :

Utiliser des APIs extérieurs à vb permet à nos projets vb de voir leurs fonctionnalités très étendu. Or vous avez déjà pu lire que utiliser des APIs sous vb, c'est moins rapide que d'utiliser les fonctions préprogrammés vb.
Je pense notamment aux manipulation de caractères. Par exemple : le petit "&" est l'équivalent de l'API lstrcat()... normalement la vitesse d'exécution devrai être la même! Mais ce n'est pas le cas.
Pourquoi? "parce que le '&' est intégré à vb". Oui, c'est un des éléments de réponse. L'autre élément à savoir est que vb n'appelle pas directement les API "déclarés" tel que le ferai un programme compilé. VB utilise une API qui appelle les API! cette fonction que l'on appelle implicitement via "Declare" s'appelle DllFunctionCall. Et lorsque vous compilez votre programme vb en .exe , toutes les apis seront appelé via DllFunctionCall avec le nom "string" de l'API, le nom "string" de la dll, et un argument "ParamArray".

Que faut-il comprendre au travers de ceci? Il faut comprendre que, malheureusement, appeler une API sous vb sera définitivement toujours un peu plus lent que dans un autre langage...
Mais attention! cela ne signifie pas qu'il ne faut plus utiliser d'apis, mais qu'il faut les utiliser astucieusement.
Utiliser une api sous l'ide semble toujours être plus rapide que les fonctions vb "reconnu lente". Cette verité ne l'est plus lorsque le programme est compilé! (j'ai pas rigolé quand je l'ai testé... la plupard des "croyances populaire" au sujets de certaines API se sont vue bafoué!)

En conclusion, il reste plus avantageux d'utiliser les ActiveX plutôt que les APIs... MAIS...
... mais uniquement dans des cas bien préçis. En effet, les activex fournis par microsoft appellent directement les apis nécessaires, et sous un .exe vb compilé, le programme appelle les fonctions le l'objet activex via __vbaObjSet (et quelques autres)... Bref, si l'activex utilise beaucoup d'apis brut, il est plus interessant de passé via l'activex. En revanche, si l'activex a été créé sous vb, ou que l'activex n'utilise pas plus d'API que l'on peut soi-même appelé via "Declare", utilisez directement les API ! C'est plus rapide.
A noté que, pour justement palier à ce problème, directx/direct3d8 intègre directement ses apis dans le .exe vb compilé!!!

Nota : un ActiveX est un objet dans un fichier dont l'extension est soit .dll, soit .ocx

Nota 2 : utiliser un activex est toujours plus pratique pour la lisibilité d'un code. Si votre objectif est de faire un "joli code de démonstration", utilisez toujours ces activex! En revanche, si vous souhaitez des performances, oubliez les règles standard de coding : trichez, imbriquez, outrepassez et detournez les fonctions vb!

4/ REMARQUE RELATIVE A LA RECUSRIVITE :
... et à l'appel de fonctions intrinsèque via Call.

Chaque fois que vous appelez une de vos fonctions utilisateur, ou qu'il y a un appel à un "evênement", etc... sachez que dans le .exe vb compilé, il y a toujours une vérification de la "pile d'instruction" via __vbaChkStk (on peut l'assimilé aux nombres de sous-sous-sous-subs() dans lequel le pointeur "ligne jaune" se situe). Cela permet de générer l'erreur "Espace de pile insuffisant". Comprenez donc que plus vous appelez de subs et de sous-subs, plus l'exécution perdra des performances pour faire cette vérification.
Bref, cela devient vraiment de la recherche de performances "capillotracté" ou "drososodomite", mais en faisant des subs "tout-en-1", on gagne en performances!

Oui, c'est anti-conventionnelle.

Source / Exemple :


'ce tutorial vient en complément de :
'http://www.vbfrance.com/article.aspx?Val=10159
'un analyseur de programme compilé.

Conclusion :


Bon je mettrai un peu a jour cette doc (probablement pour refaire la présentation et corriger des petites erreurs ici et là...) mais dans l'ensemble, le texte est complet.
Je mets "niveau 3" car ça ne s'adresse vraiment pas au débutant, ni aux personnes attachés aux conventions. C'est du domaine de la "triche" et de l'optimisation limite "bidouille" héhéhé :)

Tout ceux qui ont des astuces relative à la compilation, ajoutés-les en commentaire!

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.