web-dev-qa-db-fra.com

Concaténation efficace des chaînes en C++

J'ai entendu quelques personnes exprimer leurs inquiétudes concernant l'opérateur "+" dans std :: string et diverses solutions de contournement pour accélérer la concaténation. Est-ce que certains d'entre eux sont vraiment nécessaires? Si tel est le cas, quel est le meilleur moyen de concaténer des chaînes en C++?

89
sneg

Le travail supplémentaire n'en vaut probablement pas la peine, sauf si vous avez vraiment besoin d'efficacité. Vous obtiendrez probablement une bien meilleure efficacité simplement en utilisant l'opérateur + = à la place. 

Maintenant, après cet avertissement, je vais répondre à votre question actuelle ...

L'efficacité de la classe de chaîne 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 concaténant manuellement via c fonctions intégrées. 

Pourquoi l'opérateur + n'est pas efficace:

Jetez un oeil à cette interface:

template <class charT, class traits, class Alloc>
basic_string<charT, traits, Alloc>
operator+(const basic_string<charT, traits, Alloc>& s1,
          const basic_string<charT, traits, Alloc>& s2)

Vous pouvez voir qu'un nouvel objet est retourné après chaque +. Cela signifie qu’un nouveau tampon est utilisé à chaque fois. Si vous faites une tonne d'opérations extra +, ce n'est pas efficace. 

Pourquoi vous pouvez le rendre plus efficace:

  • Vous garantissez l'efficacité au lieu de faire confiance à un délégué pour le faire efficacement pour vous
  • la classe std :: string ne sait rien de la taille maximale de votre chaîne, ni de la fréquence à laquelle vous allez la concaténer. Vous pouvez avoir cette connaissance et peut faire des choses en ayant cette information. Cela conduira à moins de réaffectations. 
  • Vous contrôlerez les tampons manuellement afin que vous puissiez être sûr de ne pas copier la chaîne entière dans de nouveaux tampons si vous ne voulez pas que cela se produise. 
  • Vous pouvez utiliser la pile pour vos tampons au lieu du tas, ce qui est beaucoup plus efficace. 
  • string + operator créera un nouvel objet string et le retournera par conséquent en utilisant un nouveau tampon. 

Considérations pour la mise en œuvre:

  • Gardez une trace de la longueur de la chaîne.
  • Gardez un pointeur sur la fin de la chaîne et le début, ou simplement le début et utilisez le début + la longueur comme décalage pour trouver la fin de la chaîne. 
  • Assurez-vous que la mémoire tampon dans laquelle vous stockez votre chaîne est suffisamment grande pour que vous n'ayez pas besoin de réaffecter des données.
  • Utilisez strcpy au lieu de strcat pour ne pas avoir à parcourir la longueur de la chaîne pour trouver la fin de la chaîne.

Structure de données de corde:

Si vous avez besoin de concaténations très rapides, utilisez une structure de données rope .

78
Brian R. Bondy

Réservez votre dernier espace avant, puis utilisez la méthode append avec un tampon. Par exemple, supposons que votre chaîne finale compte 1 million de caractères:

std::string s;
s.reserve(1000000);

while (whatever)
{
  s.append(buf,len);
}
68
Carlos A. Ibarra

Je ne m'inquiéterais pas pour ça. Si vous le faites en boucle, les chaînes vont toujours préallouer de la mémoire pour minimiser les réaffectations - utilisez simplement operator+= dans ce cas. Et si vous le faites manuellement, quelque chose comme ça ou plus

a + " : " + c

Ensuite, il crée des temporaires - même si le compilateur pourrait éliminer certaines copies de valeurs de retour. En effet, dans un operator+ appelé successivement, il ne sait pas si le paramètre de référence fait référence à un objet nommé ou à un temporaire renvoyé par une invocation sous operator+. Je préférerais ne pas m'inquiéter avant de ne pas avoir profilé en premier. Mais prenons un exemple pour montrer cela. Nous introduisons d’abord des parenthèses pour clarifier la reliure. Je mets les arguments directement après la déclaration de fonction utilisée pour plus de clarté. Ci-dessous, je montre quelle est l'expression résultante:

((a + " : ") + c) 
calls string operator+(string const&, char const*)(a, " : ")
  => (tmp1 + c)

Maintenant, dans cet ajout, tmp1 est ce qui a été renvoyé par le premier appel à operator + avec les arguments montré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 de a et " : ". Maintenant, cela se produit:

(tmp1 + c)
calls string operator+(string const&, string const&)(tmp1, c)
  => tmp2 == <end result>

Comparez cela à ce qui suit:

std::string f = "hello";
(f + c)
calls string operator+(string const&, string const&)(f, c)
  => tmp1 == <end result>

Il utilise la même fonction pour une chaîne temporaire et une chaîne nommée! Ainsi, le compilateur doit copier l'argument dans une nouvelle chaîne, puis l'ajouter et le renvoyer à partir du corps de operator+. Il ne peut pas prendre la mémoire d'un temporaire et ajouter à cela. Plus l'expression est grande, plus le nombre de copies de chaînes à effectuer est important. 

Next Visual Studio et GCC prendront en charge la sémantique move de c ++ 1x (complétant la sémantique de la copie ) et les références rvalue en tant qu'addition expérimentale. 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 retrouvera dans un "add-pipeline" sans copies.

Si cela s'avère être un goulot d'étranglement, vous pouvez toujours faire

 std::string(a).append(" : ").append(c) ...

Les appels append ajoutent l'argument à *this, puis renvoient une référence à eux-mêmes. Donc, aucune copie des temporaires n'est faite ici. Ou alternativement, le operator+= peut être utilisé, mais vous auriez besoin de parenthèses disgracieuses pour réparer la priorité.

16

Pour la plupart des applications, ce n'est pas grave. Il vous suffit d'écrire votre code, ignorant parfaitement le fonctionnement exact de l'opérateur +, et de prendre les choses en main si cela devenait un goulet d'étranglement apparent.

11
Pesto

Contrairement à .NET System.String, C++ std :: strings est mutable, et peut donc être construit par une simple concaténation aussi rapidement que par d’autres méthodes.

7
James Curran

peut-être std :: stringstream à la place?

Mais je suis d’accord avec le sentiment que vous devriez probablement le garder facile à maintenir et compréhensible, puis profilez pour voir si vous rencontrez vraiment des problèmes. 

5
Tim

Dans Imperfect C++ , Matthew Wilson présente un concaténateur dynamic string pré-calculant la longueur de la chaîne finale afin de ne disposer que d’une allocation avant de concaténer toutes les parties. Nous pouvons également implémenter un concaténateur statique en jouant avec expression templates .

Ce type d’idée a été implémenté dans l’implémentation std :: string de STLport - cela n’est pas conforme à la norme à cause de ce hack précis.

4
Luc Hermitte

std::stringoperator+ 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::stringappend et operator+= d'autre part, augmentez la capacité de 50% chaque fois que la chaîne doit croître. Ce qui réduit considérablement le nombre d’allocations de mémoire et d’opérations de copie, O (log n).

3
timmerov

Peu importe les petites chaînes ... Si vous avez de grandes chaînes, vous feriez mieux de les stocker car 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 grosse chaîne.

Je préfère std :: ostringstream pour la concaténation complexe.

2
Mykola Golubyev

Comme pour la plupart des choses, il est plus facile de ne pas faire quelque chose que de le faire. 

Si vous souhaitez afficher des chaînes de grande taille sur une interface utilisateur graphique, il se peut que quoi que vous produisiez, vous puissiez mieux gérer les chaînes en morceaux qu'une chaîne de grande taille (par exemple, concaténer du texte dans un éditeur de texte - généralement, les lignes sont séparées). structures).

Si vous souhaitez exporter dans un fichier, diffusez les données en flux plutôt que de créer une chaîne longue et de la sortir.

Je n'ai jamais trouvé nécessaire d'accélérer la concaténation si je supprimais la concaténation inutile du code lent.

2
Pete Kirkham

Meilleure performance si vous pré-allouez (réservez) de l’espace dans la chaîne résultante. 

template<typename... Args>
std::string concat(Args const&... args)
{
    size_t len = 0;
    for (auto s : {args...})  len += strlen(s);

    std::string result;
    result.reserve(len);    // <--- preallocate result
    for (auto s : {args...})  result += s;
    return result;
}

Usage:

std::string merged = concat("This ", "is ", "a ", "test!");
0
LanDenLabs

Un tableau simple 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 grosse allocation au début.

à

https://github.com/pedro-vicente/table-string

Des repères

Pour Visual Studio 2015, génération de débogage x86, amélioration substantielle par rapport à C++ std :: string.

| API                   | Seconds           
| ----------------------|----| 
| SDS                   | 19 |  
| std::string           | 11 |  
| std::string (reserve) | 9  |  
| table_str_t           | 1  |  
0
Pedro Vicente