J'ai entendu quelques personnes exprimer des inquiétudes concernant l'opérateur "+" dans std :: string et diverses solutions de contournement pour accélérer la concaténation. Certains de ces éléments sont-ils vraiment nécessaires? Si tel est le cas, quelle est la meilleure façon de concaténer des chaînes en C ++?
c++
performance
string
concatenation
ricaner
la source
la source
libstdc++
fait cela, par exemple . Ainsi, lors de l'appel de operator + avec des temporaires, il peut atteindre des performances presque aussi bonnes - peut-être un argument en faveur de son défaut, pour des raisons de lisibilité, à moins que l'on n'ait des repères montrant qu'il s'agit d'un goulot d'étranglement. Cependant, une variadique standardiséeappend()
serait à la fois optimale et lisible ...Réponses:
Le travail supplémentaire n'en vaut probablement pas la peine, sauf si vous avez vraiment besoin d'efficacité. Vous aurez probablement une bien meilleure efficacité simplement en utilisant l'opérateur + = à la place.
Maintenant, après cet avertissement, je vais répondre à votre question réelle ...
L'efficacité de la classe de chaînes STL dépend de l'implémentation de STL que vous utilisez.
Vous pouvez garantir l'efficacité et avoir un meilleur contrôle vous-même en effectuant la concaténation manuellement via les fonctions intégrées c.
Pourquoi operator + n'est pas efficace:
Jetez un œil à cette interface:
Vous pouvez voir qu'un nouvel objet est renvoyé après chaque +. Cela signifie qu'un nouveau tampon est utilisé à chaque fois. Si vous faites une tonne d'opérations + supplémentaires, ce n'est pas efficace.
Pourquoi vous pouvez le rendre plus efficace:
Considérations pour la mise en œuvre:
Structure de données de corde:
Si vous avez besoin vraiment rapide concaténations envisager d' utiliser une structure de données de corde .
la source
Réservez votre dernier espace avant, puis utilisez la méthode append avec un tampon. Par exemple, disons que vous vous attendez à ce que la longueur de votre chaîne finale soit de 1 million de caractères:
la source
Je ne m'en soucierais pas. Si vous le faites en boucle, les chaînes préalloueront toujours la mémoire pour minimiser les réallocations - utilisez simplement
operator+=
dans ce cas. Et si vous le faites manuellement, quelque chose comme ça ou plusEnsuite, il crée des temporaires - même si le compilateur peut éliminer certaines copies de valeur de retour. En effet, dans un appel successivement,
operator+
il ne sait pas si le paramètre de référence fait référence à un objet nommé ou à un temporaire renvoyé par un sous-operator+
appel. Je préfère ne pas m'en soucier avant de ne pas avoir profilé au préalable. Mais prenons un exemple pour le montrer. Nous introduisons d'abord des parenthèses pour rendre la liaison claire. Je mets les arguments directement après la déclaration de fonction utilisée pour plus de clarté. En dessous, je montre quelle est alors l'expression résultante:Maintenant, dans cet ajout,
tmp1
est ce qui a été renvoyé par le premier appel à operator + avec les arguments affichés. Nous supposons que le compilateur est vraiment intelligent et optimise la copie de la valeur de retour. Nous nous retrouvons donc avec une nouvelle chaîne contenant la concaténation dea
et" : "
. Maintenant, cela se produit:Comparez cela à ce qui suit:
Il utilise la même fonction pour un temporaire et pour une chaîne nommée! Le compilateur doit donc copier l'argument dans une nouvelle chaîne et l'ajouter à cela et le renvoyer à partir du corps de
operator+
. Il ne peut pas prendre la mémoire d'un temporaire et y ajouter. Plus l'expression est grande, plus il faut faire de copies de chaînes.Ensuite, Visual Studio et GCC prendront en charge la sémantique de déplacement de c ++ 1x (complétant la sémantique de copie ) et les références rvalue comme ajout expérimental. Cela permet de savoir si le paramètre fait référence à un temporaire ou non. Cela rendra ces ajouts incroyablement rapides, car tout ce qui précède se terminera dans un "add-pipeline" sans copies.
Si cela s'avère être un goulot d'étranglement, vous pouvez toujours le faire
Les
append
appels ajoutent l'argument à*this
, puis renvoient une référence à eux-mêmes. Il n'y a donc pas de copie des temporaires. Ou bien, leoperator+=
peut être utilisé, mais vous auriez besoin de parenthèses laides pour fixer la priorité.la source
libstdc++
pouroperator+(string const& lhs, string&& rhs)
faitreturn std::move(rhs.insert(0, lhs))
. Ensuite, si les deux sont temporaires, sonoperator+(string&& lhs, string&& rhs)
silhs
a une capacité suffisante disponible sera juste directementappend()
. Là où je pense que cela risque d'être plus lent queoperator+=
silhs
n'a pas assez de capacité, comme alors il retomberhs.insert(0, lhs)
, ce qui doit non seulement étendre le tampon et ajouter le nouveau contenuappend()
, mais doit également se déplacer le long du contenu d'origine derhs
right.operator+=
est queoperator+
doit toujours renvoyer une valeur, donc il doit àmove()
l'opérande auquel il est ajouté. Pourtant, je suppose que c'est une surcharge assez mineure (copier quelques pointeurs / tailles) par rapport à la copie profonde de la chaîne entière, donc c'est bien!Pour la plupart des applications, cela n'a pas d'importance. Écrivez simplement votre code, ignorant parfaitement comment fonctionne l'opérateur +, et ne prenez les choses en main que si cela devient un goulot d'étranglement apparent.
la source
Contrairement à .NET System.Strings, les std :: strings de C ++ sont modifiables et peuvent donc être générées par simple concaténation tout aussi rapidement que par d'autres méthodes.
la source
operator+
n'a pas besoin de renvoyer une nouvelle chaîne. Les implémenteurs peuvent renvoyer l'un de ses opérandes, modifié, si cet opérande a été passé par référence rvalue.libstdc++
fait cela, par exemple . Ainsi, lors d'un appeloperator+
avec des temporaires, il peut obtenir des performances identiques ou presque aussi bonnes - ce qui pourrait être un autre argument en faveur de son défaut, à moins que l'on n'ait des repères montrant qu'il représente un goulot d'étranglement.peut-être std :: stringstream à la place?
Mais je suis d'accord avec le sentiment que vous devriez probablement simplement le garder maintenable et compréhensible, puis profiler pour voir si vous rencontrez vraiment des problèmes.
la source
En C ++ imparfait , Matthew Wilson présente un concaténateur de chaîne dynamique qui précalcule la longueur de la chaîne finale afin de n'avoir qu'une seule allocation avant de concaténer toutes les parties. Nous pouvons également implémenter un concaténateur statique en jouant avec des modèles d'expression .
Ce genre d'idée a été implémenté dans l'implémentation STLport std :: string - qui n'est pas conforme à la norme à cause de ce hack précis.
la source
Glib::ustring::compose()
des liaisons glibmm à GLib fait cela: estime etreserve()
s la longueur finale basée sur la chaîne de format fournie et les varargs, puisappend()
s chacun (ou son remplacement formaté) dans une boucle. Je pense que c'est une façon de travailler assez courante.std::string
operator+
alloue une nouvelle chaîne et copie les deux chaînes d'opérande à chaque fois. répétez plusieurs fois et cela devient cher, O (n).std::string
append
etoperator+=
d'autre part, augmentez la capacité de 50% à chaque fois que la chaîne doit grossir. Ce qui réduit considérablement le nombre d'allocations de mémoire et d'opérations de copie, O (log n).la source
operator+
endroit où un ou les deux arguments sont passés par référence à rvalue peuvent éviter d'allouer une nouvelle chaîne en concaténant dans le tampon existant de l'un des opérandes (même s'ils devront peut-être réallouer si sa capacité est insuffisante).Pour les petites cordes, cela n'a pas d'importance. Si vous avez de grosses chaînes, vous feriez mieux de les stocker telles qu'elles sont en vecteur ou dans une autre collection en tant que parties. Et adaptez votre algorithme pour travailler avec un tel ensemble de données au lieu d'une seule grande chaîne.
Je préfère std :: ostringstream pour la concaténation complexe.
la source
Comme pour la plupart des choses, il est plus facile de ne pas faire quelque chose que de le faire.
Si vous souhaitez afficher de grandes chaînes vers une interface graphique, il se peut que tout ce que vous produisez puisse gérer les chaînes en morceaux mieux que comme une grande chaîne (par exemple, concaténer du texte dans un éditeur de texte - généralement, ils gardent les lignes séparées structures).
Si vous souhaitez générer une sortie dans un fichier, diffusez les données plutôt que de créer une grande chaîne et de la générer.
Je n'ai jamais trouvé le besoin de rendre la concaténation plus rapide si je supprimais la concaténation inutile du code lent.
la source
Probablement meilleures performances si vous pré-allouez (réservez) de l'espace dans la chaîne résultante.
Usage:
la source
Un simple tableau de caractères, encapsulé dans une classe qui garde la trace de la taille du tableau et du nombre d'octets alloués, est le plus rapide.
L'astuce consiste à ne faire qu'une seule grande allocation au départ.
à
https://github.com/pedro-vicente/table-string
Benchmarks
Pour Visual Studio 2015, build de débogage x86, amélioration substantielle par rapport à C ++ std :: string.
la source
std::string
. Ils ne demandent pas une classe de chaînes alternative.Vous pouvez essayer celui-ci avec des réservations de mémoire pour chaque élément:
la source