Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Concaténer beaucoup de String

19 réponses
Avatar
Jean-Nicolas BERGER
Bonjour,
J'ai un souci de performances lié au fonctionnement interne de la "somme" de
chaines de caractères sous VB6 (allocation d'espace cible puis recopie de la
chaine 1 puis de la chaine 2), tandis que certains langages (VB.NET par
exemple) proposent une méthode .Append capable de réserver un espace mémoire
à la suite de la chaine 1, pour n'avoir qu'à recopier la chaine 2.
Dans mon cas, j'ai un tableau contenant beaucoup (quelques milliers) de
"petites" String (entre 10 et 50 octets), que j'ai besoin de concaténer dans
une seule chaine résultante. Or je me suis aperçu que les temps de réponses
augmentent de manière exponentielle avec le nombre de petites chaines.
Pour l'instant, pour première optimisation, j'ai juste implémenté un
comptage du nombre de lignes du tableau, pour couper en 4 morceaux, faire
des chaines plus petites, et au final faire chaine1+chaine2+chaine3+chaine4.
Et ça va déjà près de deux fois plus vite qu'en faisant une seule boucle
FOR.
Je pense toutefois qu'il doit exister un système beaucoup plus efficace,
s'appuyant par exemple sur une notion de dichotomie, qui permettrait
d'optimiser réellement la chose.
Quelqu'un aurait-il sous la main un code qui pourrait satisfaire mon besoin?
Merci d'avance pour votre aide.
JN BERGER

9 réponses

1 2
Avatar
Renfield
En train de benchmarker, le code de jean marc est très correct !!

la deuxieme solution est rapide, mais donne un resultat erroné...
je fais methode classique (naïve)
puis classe de jean marc, et la, je compare le resultat à la solution
naïve.

le code de François ne passe pas cette vérification...


mon bench entre mon code et jean marc nous dit que mon code est plus
rapide a partir d'un grand nombre de concaténations...
ou de morceaux de taille un peu plus importantes

avec 20000 iterations

StartTime = GetTickCount
Set C = New Class3
For i = 0 To MAX
C.Append "Bonjour tout le monde !" & i
Next i
s = C.Value
MsgBox "Class3 : " & GetTickCount - StartTime

JM: 125
Moi: 32
Avatar
jean-marc
Hello Renfield,


"Renfield" wrote in message
news:
En train de benchmarker, le code de jean marc est très correct !!





J'étais aussi en train de faire la même chose!


la deuxieme solution est rapide, mais donne un resultat erroné...
je fais methode classique (naïve)
puis classe de jean marc, et la, je compare le resultat à la solution
naïve.

le code de François ne passe pas cette vérification...


mon bench entre mon code et jean marc nous dit que mon code est plus
rapide a partir d'un grand nombre de concaténations...
ou de morceaux de taille un peu plus importantes





Je confirme tes résultats, mais avec des écarts beaucoup moins importants.
(En fait ça dépend fort de la taille du buffer chunck au regard du nombre de
réallocations nécessaires.

Pour un grand nombre de concaténation prévues, il faut voir grand et
dimensionner la variable avec une valeur du genre 65535.

On obtient alors des résultats très proches entre nos 2 implémentations:
http://users.skynet.be/candide/bench/Book2.htm

Ceci dit, ta méthode (Extandable Array + Join()) est une très bonne méthode,
à conseiller! Le seul problème est peut être
que Join() n'était me semble t'il pas dispo avec VB5, mais je n'en suis plus
sur.

Bonne journée :-)


--
Jean-marc Noury (jean_marc_n2)
Microsoft MVP - Visual Basic
mailto: remove '_no_spam_' ;
FAQ VB: http://faq.vb.free.fr/
Avatar
François Picalausa
On Aug 14, 11:43 am, Renfield wrote:
En train de benchmarker, le code de jean marc est très correct !!

la deuxieme solution est rapide, mais donne un resultat erroné...
je fais methode classique (naïve)
puis classe de jean marc, et la, je compare le resultat à la solution
naïve.

le code de François ne passe pas cette vérification...

mon bench entre mon code et jean marc nous dit que mon code est plus
rapide a partir d'un grand nombre de concaténations...
ou de morceaux de taille un peu plus importantes

avec 20000 iterations

StartTime = GetTickCount
Set C = New Class3
For i = 0 To MAX
C.Append "Bonjour tout le monde !" & i
Next i
s = C.Value
MsgBox "Class3 : " & GetTickCount - StartTime

JM: 125
Moi: 32



Hello,

Ma seconde solution utilisant la concaténation de l'array donne un
résultat correct à un ToString = result près...
Néanmoins, il serait intéressant de connaitre la vitesse de Join en
rapport de la méthode de concaténation "à la join" que j'employais
dans ToString (le reste du code étant identique au tiens).

Pour les performances, nous en avons discuté Jean-Marc et moi, il faut
aussi considérer l'influence des tailles de buffer interne. Dans ton
exemple, si la concaténation de string sans tableau a un buffer de
20000*(Len("Bonjour tout le monde !")+5), tu auras un net gain de
performances. Et il n'est pas irréaliste de commencer par une
allocation de 10-100Ko de mémoire au vu des spécifications modernes.

Autre chose, tu mesure le temps de création de la classe (Set C = New
Class3). Celle-ci n'est *jamais* (ou presque) a prendre en compte,
dans la mesure où on a généralement "assez de temps" pour créer
l'objet (si ça prennait trop de temps, on mettrait cette création au
démarrage, derrière un splash scren).

Une chose qui prend énormément de temps, au contraire, dans la méthode
des tableaux est la copie de chaque string dans ce tableau (et
l'allocation mémoire qui va avec).
De même, si tu fais une mesure sur plusieurs appels à ToString (plutôt
qu'a Append), tu te rendras compte qu'on peut nettement optimiser la
méthode par tableaux en copiant le résultat dans l'index 0 et en
redimensionnant le nombre d'éléments. C'est uniquement comme ça que
des méthodes effectuant des opérations sur la chaine interne (Mid,
Left, Delete, ou que sais-je) peuvent être exposées sans un coût en
performances délirant.

Voila a peu près ce qu'il faut savoir en benchant cela...
(question bête... c'était bien un bench compilé? dans ce cas, quelles
optimisations avaient été utilisées? - la non vérification des limi es
du tableau peut accélérer la méthode par tableau)

François
Avatar
Renfield
bien vu, jean marc, pas de Join en VB5

voir sur vbspeed z'ont des Join de remplacement, optimisés
Avatar
Renfield
Francois

Autre chose, tu mesure le temps de création de la classe (Set C = New
Class3). Celle-ci n'est *jamais* (ou presque) a prendre en compte,



C'est un temps constant, négligeable dans la mesure.
Ca coute pas plus cher de l'ajouter...

l'idée qui réside derrière mon code, c'est que la chaine de caractè re
globale n'est jamais stockée de manière contigüe en mémoire.
de ce fait, pas trop besoin de réallouer plus grand pour recopier le
tout...
on alloue juste des Strings de taille bien plus modérée.
Avatar
François Picalausa
Hello,

Tout d'abord, mes excuses pour le ton de mon post précédent, qui a la
relecture était peut-être un peu trop... un post du matin, mal
réveillé ;-)

On Aug 14, 1:42 pm, Renfield wrote:
Francois

> Autre chose, tu mesure le temps de création de la classe (Set C = N ew
> Class3). Celle-ci n'est *jamais* (ou presque) a prendre en compte,

C'est un temps constant, négligeable dans la mesure.
Ca coute pas plus cher de l'ajouter...



En fait, le problème est que quand tu réalise la comparaison avec
d'autres méthodes, le temps d'initialisation peut être relativement
différents. Naturellement, les mesures absolues ne sont pas affectées
et ça permet certainement d'avoir une idée du temps d'exécution...
Mais vu qu'on bench pour comparer la vitesse des différentes méthodes,
c'est important.

l'idée qui réside derrière mon code, c'est que la chaine de caract ère
globale n'est jamais stockée de manière contigüe en mémoire.
de ce fait, pas trop besoin de réallouer plus grand pour recopier le
tout...
on alloue juste des Strings de taille bien plus modérée.



Bien entendu. La méthode 2 que je présentais est basée sur exactement
la même idée. C'est pourquoi je soulignais qu'il serait intéressant de
comparer Join à une méthodes d'allocation et de copie par Mid (qui est
en réalité une implémentation très spécifique de la méthode 1),
puisque la seule différence entre nos deux codes vient de là.
Néanmoins, comme tu l'as indiqué, VBSpeed propose déjà des Join
optimisés.

L'astuce résidant dans leur Join5, 6 et 9 est la même: préallouer la
taille idéale et tout copier dans le buffer; la seule différence dans
la 9 étant l'appel a des API permettant un travail plus efficace. Par
exemple, SysAllocStringByteLen leur permet d'éviter l'initialisation
(inutile ici) réalisée par String$. Par contre, leur CopyMemory
(probablement plus rapide qu'un mid?) me laisse perplexe.

A la relecture de mon code, il y a un problème au niveau de la ligne
If mNOL > UBound(mStrings) Then
qui se compare a la tienne
If mnCount = mnCapacity Then
Ceci peut introduire une perte de vitesse certaine pour deux raisons:
- L'appel à la fonction UBound (appeler une fonction n'est pas ce
qu'il y a de plus efficace pour... déférencer un pointeur)
- La comparaison (>) est plus lente que l'égalité.
Ceci est probablement la seconde source de perte de vitesse de mon
implémentation.

En conclusion, malgré que le principe utilisé soit le même, deux
implémentations présentant de petites différences peuvent avoir des
performances très différentes (à savoir, 50% plus lent que la métho de
1 pour mon implémentation, à certainement 15% plus rapide pour la
tienne).

François
Avatar
jean-marc
"Renfield" wrote in message
news:
bien vu, jean marc, pas de Join en VB5
voir sur vbspeed z'ont des Join de remplacement, optimisés



Oui c'est sur qu'il y a des solutions de remplacement!
Et avec un join optimisé, la solution des tableaux est
vraiment bonne, à la remarque de françois près: les
performances se dégradent pas mal si on a fréquemment besoin
de consulter le résultat avec toString(). C'est un peu moins
sensible avec la méthode Mid.

Bon maintenant, restons raisonnable: les différences de vitesse
entre les diverses implémentations sont ridiculement petites,
et les performances extrèmement élevée, dans les 2 cas:

Entre 120 et 150 millisecondes pour 160000 concaténations,
c'est énorme, et du coup ça rend les différences infimes!

--
Jean-marc Noury (jean_marc_n2)
Microsoft MVP - Visual Basic
mailto: remove '_no_spam_' ;
FAQ VB: http://faq.vb.free.fr/
Avatar
Gloops
Jean-marc a écrit, le 21/07/2007 10:34 :
Bonjour,

Et oui, il est connu que la concaténation via "&" est horriblement le nte.



Bonsoir,

J'ai lu un début d'explication, un jour : la concaténation avec "&"
concatène des variants, forcément plus lourds à gérer, alors que + entre
deux chaînes de caractères concatène les chaînes en tant que tell es.

Sur ce, je n'approfondis pas plus : je devrais déjà dormir.
Avatar
Jean-Marc
On 16 août, 23:37, Gloops wrote:
Jean-marc a écrit, le 21/07/2007 10:34 :

> Bonjour,

> Et oui, il est connu que la concaténation via "&" est horriblement le nte.

Bonsoir,

J'ai lu un début d'explication, un jour : la concaténation avec "&"
concatène des variants, forcément plus lourds à gérer, alors que + entre
deux chaînes de caractères concatène les chaînes en tant que tell es.

Sur ce, je n'approfondis pas plus : je devrais déjà dormir.



Hello gloops,

ce n'est pas vraiment le problème. Que l'on utilise + ou &, ce sera de
toute façon très (très très) lent, car VB concatène naïvement: à
chaque concaténation, il réalloue de la mémoire pour la nouvelle
chaine, et recopie le nouveau contenu dans la place mémoire
nouvellement allouée (et doit libérer la mémoire de l'ancienne
chaine).
Or l'allocation et la désallocation sont 2 opérations affreusement
couteuses. C'est ce qui prend du temps. Les méthodes alternatives
proposées tant par François, Renfield ou moi même évitent ces
couteuses opérations, d'ou une améliration des perfs d'un énorme voire
gigantesque facteur.

Voila :-)

--
Jean-Marc
1 2