Comment réaliser la concaténation de chaînes de caractères rapidement ?Lorsque de nombreuses concaténations sont nécessaires, l'opérateur & souffre d'un terrible manque de performances. Nous expliquerons dans cet article la cause du problème et deux solutions possibles pour y remédier. Il est à remarquer que si la concaténation avec l'opérateur + est possible, elle ne présente aucun gain en performances et n'a pas la sémantique d'une concaténation. Ceci peut mener à des erreurs de compilation ou d'exécution. Dans tous les cas, il s'agit d'un usage déconseillé de l'opérateur +. Un problème d'allocationsAfin d'expliquer la lenteur d'une concaténation, il est important de déterminer les différentes étapes impliquées dans sa réalisation. La concaténation est réalisée par :
Les allocations mémoire sont des opérations particulièrement lentes. Leur répétition fréquentes entraîne donc nécessairement un impact sur les performances. Le but du jeu consistera donc à minimiser le nombre de ré-allocation, au détriment d'un peu de mémoire allouée de façon supeflue. Une allocation intelligente de la mémoireUne solution possible est d'allouer suffisamment de mémoire pour contenir le résultat de l'ensemble des concaténations. Il ne reste alors qu'à réaliser les différentes copies, opération relativement rapide. Dans la pratique, la taille totale du résultat est généralement inconnue. Il s'agit alors d'allouer une chaîne de caractères "suffisamment grande" et de la redimensionner si nécessaire. On comprend aisément qu'il s'agit de limiter le nombre de ré-allocations et donc que la taille allouée soit supérieure ou égale à la taille du résultat final pour obtenir les meilleures performances. Voici un exemple d'implémentation de cette technique. La fonction String permettra l'allocation de mémoire et l'instruction Mid$ réalisera la copie de données. Le code suivant est à placer dans un module de classe : Option Explicit Limiter la mémoire utiliséeUn désavantage de la méthode précédente est que, si la taille est mal évaluée, on peut perdre énormément de place en mémoire. Pour se rapprocher de l'allocation optimale, il faudrait connaître la taille totale du résultat et donc l'ensemble des concaténations à effectuer. Ceci est possible en conservant dans un tableau dynamique — dont la taille sera de préférence allouée par bloc, afin de conserver de bonnes performances malgré la faible perte de mémoire — l'ensemble des chaînes à concaténer. Voici un exemple d'implémentation de cette technique. Le code suivant est à placer dans un module de classe : Option Explicit Comparaison des deux méthodesNous avons écrit le code précédent de sorte que les appels soient indépendants de la méthode, afin de nous concentrer sur les différences et similitudes réelles. Voici un exemple d'utilisation : Dim sb As CStringBuilder Les deux méthodes peuvent présenter des performances déplorables ou excellentes en fonction des valeurs d'allocations et du problème à traiter. Dans la pratique, les deux méthodes seront équivalentes en performances, pour de "bonnes" valeurs de BlockCount. Il est à remarquer que la signification, de même que la valeur, de BlockCount est très différente dans les deux cas. Les deux méthodes allouent un peu d'espace "inutilement". Néanmoins, sur les systèmes modernes, allouer plus de mémoire que nécessaire n'est pas problématique, particulièrement lorsque de gros volumes de données sont à gérer rapidement. Augmenter les valeurs de blocksize à des valeurs plus hautes que celles proposées par défaut n'est donc pas une mauvaise idée. Il est important de remarquer que le travail est réalisé à deux endroits très différents entre les méthodes. La première méthode réalise le résultat final au fur et à mesure de nouveaux ajouts. La seconde méthode ne réalise le résultat final que lors de l'appel à ToString. Le résultat généré, dans ce dernier cas, est mis en cache dans le premier élément du tableau. Ceci évite une dégradation des performances lors de plusieurs appels successifs à ToString. Il serait tentant d'ignorer la première méthode au seul profit de la seconde, vu les performances semblables et l'économie de mémoire. Néanmoins, un point important doit être évoqué, celui de la manipulation de chaînes. La manipulation de chaînes comprend l'extraction d'une partie de la chaîne de caractères, la suppression de caractères en fin, etc. L'implémentation dans le cas de la première méthode est évidente : employer les fonctions internes de VB sur le buffer interne de la classe et/ou manipuler le pointeur de fin de chaîne est entièrement suffisant. Dans le second cas, il est nécessaire de construire complètement la chaîne de caractère, et ensuite appliquer la modification. L'implémentation de telles méthodes peut donc présenter de moins bonnes performances et, de manière générale, est légèrement plus compliquée à réaliser. Ce sont donc les méthodes de manipulation souhaitables et l'occupation mémoire qui dicteront le choix d'une méthode ou d'une autre. Dans les deux cas, on a des performances nettement meilleures que par la concaténation avec l'opérateur &. Un test, avec des blocksize correctement paramétrés, pour 16000 concaténations, la concaténation par & prenait une minute, contre 60 millisecondes pour un stringbuilder. Cette constatation est générale : des gains situés entre 500 % et 1000 % sont courants. |
Date de publication : 13 septembre 2007 Dernière modification : 13 septembre 2007 Rubriques : Texte & strings Mots-clés : string, concaténation, vitesse, buffer, préallocation, stringbuilder |