Est String.Format aussi efficace que StringBuilder

160

Supposons que j'ai un constructeur de chaînes en C # qui fait ceci:

StringBuilder sb = new StringBuilder();
string cat = "cat";
sb.Append("the ").Append(cat).(" in the hat");
string s = sb.ToString();

serait-ce aussi efficace ou plus efficace que d'avoir:

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);

Si oui, pourquoi?

ÉDITER

Après quelques réponses intéressantes, j'ai réalisé que j'aurais probablement dû être un peu plus clair dans ce que je demandais. Je n'ai pas tellement demandé ce qui était le plus rapide pour concaténer une chaîne, mais ce qui est plus rapide pour injecter une chaîne dans une autre.

Dans les deux cas ci-dessus, je souhaite injecter une ou plusieurs chaînes au milieu d'une chaîne de modèle prédéfinie.

Désolé pour la confusion

lomaxx
la source
Veuillez les laisser ouvertes pour permettre de futures améliorations.
Mark Biek le
4
Dans un cas particulier, le plus rapide n'est ni l'un ni l'autre: si la pièce à remplacer a la même taille que la nouvelle pièce, vous pouvez modifier la chaîne sur place. Malheureusement, cela nécessite une réflexion ou un code non sécurisé et viole délibérément l'immuabilité de la chaîne. Pas une bonne pratique, mais si la vitesse est un problème ... :)
Abel
dans l'exemple donné ci string s = "The "+cat+" in the hat";- dessus pourrait être le plus rapide à moins qu'il ne soit utilisé dans une boucle, auquel cas le plus rapide sera avec un StringBuilder initialisé en dehors de la boucle.
Surya Pratap

Réponses:

146

REMARQUE: cette réponse a été écrite lorsque .NET 2.0 était la version actuelle. Cela peut ne plus s'appliquer aux versions ultérieures.

String.Formatutilise un en StringBuilderinterne:

public static string Format(IFormatProvider provider, string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }

    StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
    builder.AppendFormat(provider, format, args);
    return builder.ToString();
}

Le code ci-dessus est un extrait de mscorlib, donc la question devient "est StringBuilder.Append()plus rapide que StringBuilder.AppendFormat()"?

Sans analyse comparative, je dirais probablement que l'exemple de code ci-dessus fonctionnerait plus rapidement en utilisant .Append(). Mais c'est une supposition, essayez de comparer et / ou de profiler les deux pour obtenir une comparaison correcte.

Ce type, Jerry Dixon, a fait des analyses comparatives:

http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

Actualisé:

Malheureusement, le lien ci-dessus est mort depuis. Cependant, il y en a encore une copie sur la Way Back Machine:

http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

À la fin de la journée, cela dépend si le formatage de votre chaîne sera appelé de manière répétitive, c'est-à-dire que vous effectuez un traitement de texte sérieux sur 100 mégaoctets de texte, ou s'il est appelé lorsqu'un utilisateur clique de temps en temps sur un bouton. À moins que vous ne fassiez un énorme travail de traitement par lots, je m'en tiendrai à String.Format, cela facilite la lisibilité du code. Si vous soupçonnez un goulot d'étranglement des performances, collez un profileur sur votre code et voyez où il se trouve réellement.

Kev
la source
8
Un problème avec les benchmarks sur la page de Jerry Dixon est qu'il n'appelle jamais .ToString()l' StringBuilderobjet. Sur un grand nombre d'itérations, ce temps fait une grande différence et signifie qu'il ne compare pas tout à fait des pommes avec des pommes. C'est la raison pour laquelle il montre de si bonnes performances StringBuilderet explique probablement sa surprise. Je viens de répéter le benchmark en corrigeant cette erreur et j'ai obtenu les résultats escomptés: l' String +opérateur a été le plus rapide, suivi de StringBuilder, String.Formaten remontant l'arrière.
Ben Collins
5
6 ans plus tard, ce n'est plus tout à fait le cas. Dans Net4, string.Format () crée et met en cache une instance de StringBuilder qu'il réutilise, donc il peut dans certains cas de test être plus rapide que StringBuilder. J'ai mis un benchmark révisé en réponse ci-dessous (qui dit toujours que concat est le plus rapide et pour mon cas de test, le format est 10% plus lent que StringBuilder).
Chris F Carroll
45

À partir de la documentation MSDN :

Les performances d'une opération de concaténation pour un objet String ou StringBuilder dépendent de la fréquence à laquelle une allocation de mémoire se produit. Une opération de concaténation String alloue toujours de la mémoire, tandis qu'une opération de concaténation StringBuilder alloue uniquement de la mémoire si le tampon d'objet StringBuilder est trop petit pour accueillir les nouvelles données. Par conséquent, la classe String est préférable pour une opération de concaténation si un nombre fixe d'objets String est concaténé. Dans ce cas, les opérations de concaténation individuelles peuvent même être combinées en une seule opération par le compilateur. Un objet StringBuilder est préférable pour une opération de concaténation si un nombre arbitraire de chaînes est concaténé; par exemple, si une boucle concatène un nombre aléatoire de chaînes d'entrée utilisateur.

Greg
la source
12

J'ai exécuté quelques tests de performances rapides, et pour 100000 opérations en moyenne sur 10 exécutions, la première méthode (String Builder) prend presque la moitié du temps de la seconde (String Format).

Donc, si cela n'est pas fréquent, cela n'a pas d'importance. Mais s'il s'agit d'une opération courante, vous pouvez utiliser la première méthode.

Vaibhav
la source
10

Je m'attendrais à ce que String.Format soit plus lent - il doit analyser la chaîne, puis la concaténer.

Quelques notes:

  • Le format est la voie à suivre pour les chaînes visibles par l'utilisateur dans les applications professionnelles; cela évite les bogues de localisation
  • Si vous connaissez au préalable la longueur de la chaîne résultante, utilisez le constructeur StringBuilder (Int32) pour prédéfinir la capacité
McDowell
la source
8

Je pense que dans la plupart des cas, cette clarté, et non l'efficacité, devrait être votre plus grande préoccupation. À moins que vous n'écrasiez des tonnes de cordes ou que vous construisiez quelque chose pour un appareil mobile moins puissant, cela ne fera probablement pas beaucoup de différence dans votre vitesse de course.

J'ai trouvé que, dans les cas où je construis des chaînes de manière assez linéaire, faire des concaténations droites ou utiliser StringBuilder est votre meilleure option. Je suggère cela dans les cas où la majorité de la chaîne que vous construisez est dynamique. Comme très peu de texte est statique, le plus important est qu'il soit clair où chaque morceau de texte dynamique est placé au cas où il aurait besoin d'être mis à jour à l'avenir.

D'un autre côté, si vous parlez d'un gros morceau de texte statique avec deux ou trois variables, même si c'est un peu moins efficace, je pense que la clarté que vous gagnez avec la chaîne en vaut la peine. Je l'ai utilisé plus tôt cette semaine lorsque j'ai dû placer un morceau de texte dynamique au centre d'un document de 4 pages. Il sera plus facile de mettre à jour ce gros morceau de texte s'il est en un seul morceau que d'avoir à mettre à jour trois morceaux que vous concaténez.

Saalon
la source
Oui! Utilisez String.Format lorsque cela a du sens, c'est-à-dire lorsque vous formatez des chaînes. Utilisez la concaténation de chaînes ou un StringBuilder lorsque vous effectuez une concaténation mécanique. Efforcez-vous toujours de choisir la méthode qui communique votre intention au prochain responsable.
Rob
8

Ne serait-ce que parce que string.Format ne fait pas exactement ce que vous pourriez penser, voici une reprise des tests 6 ans plus tard sur Net45.

Concat est toujours le plus rapide mais il y a vraiment moins de 30% de différence. StringBuilder et Format diffèrent à peine de 5 à 10%. J'ai eu des variations de 20% en exécutant les tests plusieurs fois.

Millisecondes, un million d'itérations:

  • Concaténation: 367
  • Nouveau stringBuilder pour chaque clé: 452
  • StringBuilder mis en cache: 419
  • string.Format: 475

La leçon que je retire est que la différence de performances est insignifiante et qu'elle ne devrait donc pas vous empêcher d'écrire le code lisible le plus simple possible. Ce qui pour mon argent est souvent mais pas toujours a + b + c.

const int iterations=1000000;
var keyprefix= this.GetType().FullName;
var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations);
Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength);

var concatkeys= new string[iterations];
var stringbuilderkeys= new string[iterations];
var cachedsbkeys= new string[iterations];
var formatkeys= new string[iterations];

var stopwatch= new System.Diagnostics.Stopwatch();
Console.WriteLine("Concatenation:");
stopwatch.Start();

for(int i=0; i<iterations; i++){
    var key1= keyprefix+":" + i.ToString();
    concatkeys[i]=key1;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("New stringBuilder for each key:");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString();
    stringbuilderkeys[i]= key2;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("Cached StringBuilder:");
var cachedSB= new StringBuilder(maxkeylength);
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString();
    cachedsbkeys[i]= key2b;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("string.Format");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key3= string.Format("{0}:{1}", keyprefix,i.ToString());
    formatkeys[i]= key3;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-');
Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);
Chris F Carroll
la source
2
Par "string.Format ne fait pas exactement ce que vous pourriez penser", je veux dire que dans le code source 4.5, il essaie de créer et de réutiliser une instance de StringBuilder en cache. J'ai donc inclus cette approche dans le test
Chris F Carroll
6

String.Format utilise en StringBuilderinterne ... donc logiquement cela conduit à l'idée qu'il serait un peu moins performant en raison de plus de frais généraux. Cependant, une simple concaténation de chaînes est la méthode la plus rapide pour injecter une chaîne entre deux autres ... de manière significative. Cette preuve a été démontrée par Rico Mariani dans son tout premier Quiz Performance, il y a des années. Le simple fait est que les concaténations ... lorsque le nombre de parties de cordes est connu (sans limitation..vous pouvez concaténer mille parties ... tant que vous connaissez toujours ses 1000 parties) ... sont toujours plus rapides que StringBuilderou String. Format. Ils peuvent être effectués avec une seule allocation de mémoire et une série de copies de mémoire. Voici la preuve

Et voici le code réel de certaines méthodes String.Concat, qui appellent finalement FillStringChecked qui utilise des pointeurs pour copier la mémoire (extraite via Reflector):

public static string Concat(params string[] values)
{
    int totalLength = 0;

    if (values == null)
    {
        throw new ArgumentNullException("values");
    }

    string[] strArray = new string[values.Length];

    for (int i = 0; i < values.Length; i++)
    {
        string str = values[i];
        strArray[i] = (str == null) ? Empty : str;
        totalLength += strArray[i].Length;

        if (totalLength < 0)
        {
            throw new OutOfMemoryException();
        }
    }

    return ConcatArray(strArray, totalLength);
}

public static string Concat(string str0, string str1, string str2, string str3)
{
    if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null)))
    {
        return Empty;
    }

    if (str0 == null)
    {
        str0 = Empty;
    }

    if (str1 == null)
    {
        str1 = Empty;
    }

    if (str2 == null)
    {
        str2 = Empty;
    }

    if (str3 == null)
    {
        str3 = Empty;
    }

    int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length;
    string dest = FastAllocateString(length);
    FillStringChecked(dest, 0, str0);
    FillStringChecked(dest, str0.Length, str1);
    FillStringChecked(dest, str0.Length + str1.Length, str2);
    FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3);
    return dest;
}

private static string ConcatArray(string[] values, int totalLength)
{
    string dest = FastAllocateString(totalLength);
    int destPos = 0;

    for (int i = 0; i < values.Length; i++)
    {
        FillStringChecked(dest, destPos, values[i]);
        destPos += values[i].Length;
    }

    return dest;
}

private static unsafe void FillStringChecked(string dest, int destPos, string src)
{
    int length = src.Length;

    if (length > (dest.Length - destPos))
    {
        throw new IndexOutOfRangeException();
    }

    fixed (char* chRef = &dest.m_firstChar)
    {
        fixed (char* chRef2 = &src.m_firstChar)
        {
            wstrcpy(chRef + destPos, chRef2, length);
        }
    }
}

Donc alors:

string what = "cat";
string inthehat = "The " + what + " in the hat!";

Prendre plaisir!

jrista
la source
dans Net4, string.Format met en cache et réutilise une instance de StringBuilder et peut donc être plus rapide dans certaines utilisations.
Chris F Carroll
3

Oh aussi, le plus rapide serait:

string cat = "cat";
string s = "The " + cat + " in the hat";
Vaibhav
la source
non, la concaténation de chaînes est extrêmement lente, car .NET crée des copies supplémentaires de vos variables de chaîne entre les opérations de concaténation, dans ce cas: deux copies supplémentaires plus la copie finale de l'affectation. Résultat: des performances extrêmement médiocres par rapport à celles StringBuilderqui sont faites pour optimiser ce type de codage en premier lieu.
Abel
Le plus rapide à taper peut-être;)
UpTheCreek
2
@Abel: La réponse manque peut-être de détails, mais cette approche EST l'option la plus rapide, dans cet exemple particulier. Le compilateur le transformera en un seul appel String.Concat (), donc le remplacement par un StringBuilder ralentira en fait le code.
Dan C.
1
@Vaibhav est correct: dans ce cas, la concaténation est la plus rapide. Bien sûr, la différence serait insignifiante à moins d'être répétée un grand nombre de fois, ou peut-être opérée sur une corde beaucoup, beaucoup plus grande.
Ben Collins
0

Cela dépend vraiment. Pour les petites chaînes avec peu de concaténations, il est en fait plus rapide d'ajouter simplement les chaînes.

String s = "String A" + "String B";

Mais pour des chaînes plus grandes (très très grandes chaînes), il est alors plus efficace d'utiliser StringBuilder.

Joseph Daigle
la source
0

Dans les deux cas ci-dessus, je souhaite injecter une ou plusieurs chaînes au milieu d'une chaîne de modèle prédéfinie.

Dans ce cas, je suggérerais que String.Format est le plus rapide car il est conçu dans ce but précis.

GateKiller
la source
-1

Je suggérerais de ne pas le faire, puisque String.Format n'a pas été conçu pour la concaténation, il a été conçu pour formater la sortie de diverses entrées telles qu'une date.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
GateKiller
la source