Le moyen le plus efficace de concaténer les chaînes? [c#]


Answers

Rico Mariani , le gourou .NET Performance, avait un article sur ce sujet. Ce n'est pas aussi simple qu'on pourrait le penser. Le conseil de base est celui-ci:

Si votre motif ressemble à:

x = f1(...) + f2(...) + f3(...) + f4(...)

c'est une concat et c'est zippy, StringBuilder ne va probablement pas aider.

Si votre motif ressemble à:

if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)

alors vous voulez probablement StringBuilder.

Encore un autre article pour soutenir cette affirmation vient d'Eric Lippert où il décrit les optimisations effectuées sur une ligne + concaténations de manière détaillée.

Question

Quel est le moyen le plus efficace de concaténer les chaînes?




De Chinh Do - StringBuilder n'est pas toujours plus rapide :

Règles de base

  • Lorsque vous enchaînez trois valeurs de chaînes dynamiques ou moins, utilisez la concaténation de chaînes traditionnelle.

  • Lorsque vous concaténez plus de trois valeurs de chaînes dynamiques, utilisez StringBuilder.

  • Lors de la construction d'une grande chaîne à partir de plusieurs chaînes littérales, utilisez le littéral @ chaîne ou l'opérateur inline +.

La plupart du temps StringBuilder est votre meilleur pari, mais il y a des cas comme indiqué dans ce post que vous devriez au moins penser à chaque situation.




De cet article MSDN :

Une surcharge est associée à la création d'un objet StringBuilder, à la fois dans le temps et dans la mémoire. Sur une machine avec mémoire rapide, un StringBuilder devient intéressant si vous faites environ cinq opérations. En règle générale, je dirais 10 opérations de chaîne ou plus est une justification de la surcharge sur n'importe quelle machine, même une plus lente.

Donc, si vous faites confiance à MSDN allez avec StringBuilder si vous devez faire plus de 10 opérations / concaténations de chaînes de caractères - sinon la concaténation de chaîne simple avec '+' est bien.










Essayez ce 2 morceaux de code et vous trouverez la solution.

 static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10000000; i++)
        {
            s.Append( i.ToString());
        }
        Console.Write("End");
        Console.Read();
    }

Contre

static void Main(string[] args)
    {
        string s = "";
        for (int i = 0; i < 10000000; i++)
        {
            s += i.ToString();
        }
        Console.Write("End");
        Console.Read();
    }

Vous trouverez que le 1er code se terminera très rapidement et la mémoire sera dans une bonne proportion.

Le second code peut-être que la mémoire ira bien, mais cela prendra plus de temps ... beaucoup plus longtemps. Donc, si vous avez une application pour beaucoup d'utilisateurs et que vous avez besoin de vitesse, utilisez le 1er. Si vous avez une application pour une application d'utilisateur à court terme, vous pouvez peut-être utiliser les deux ou la deuxième sera plus «naturelle» pour les développeurs.

À votre santé.




Pour seulement deux chaînes, vous ne voulez certainement pas utiliser StringBuilder. Il existe un seuil au-dessus duquel le surcoût StringBuilder est inférieur au temps système d'allocation de plusieurs chaînes.

Donc, pour plus de 2-3 chaînes, utilisez le code de DannySmurf . Sinon, utilisez simplement l'opérateur +.




Voici la méthode la plus rapide que j'ai développée pendant une décennie pour mon application NLP à grande échelle. J'ai des variations pour IEnumerable<T> et d'autres types d'entrées, avec et sans séparateurs de types différents ( Char , String ), mais ici je montre le cas simple de concaténer toutes les chaînes d'un tableau en une seule chaîne, sans séparateur. La dernière version ici est développée et testée sur C # 7 et .NET 4.7 .

Il y a deux clés pour une meilleure performance. le premier est de pré-calculer la taille totale exacte requise. Cette étape est triviale lorsque l'entrée est un tableau comme indiqué ici. Pour gérer IEnumerable<T> place, il faut d'abord rassembler les chaînes dans un tableau temporaire pour calculer ce total (Le tableau est requis pour éviter d'appeler ToString() plus d'une fois par élément car techniquement, vu la possibilité d'effets secondaires, cela pourrait changer la sémantique attendue d'une opération 'jointure de chaîne').

Ensuite, étant donné la taille d'allocation totale de la chaîne finale, la plus grande amélioration des performances est obtenue en construisant la chaîne de résultats sur place . Cela nécessite la technique (peut-être controversée) de suspendre temporairement l'immutabilité d'une nouvelle String initialement allouée pleine de zéros. Une telle controverse de côté, cependant ...

... notez que c'est la seule solution de concaténation de masse sur cette page qui évite entièrement un tour supplémentaire d'allocation et de copie par le constructeur String .

Code complet:

/// <summary>
/// Concatenate the strings in 'rg', none of which may be null, into a single String.
/// </summary>
public static unsafe String StringJoin(this String[] rg)
{
    int i;
    if (rg == null || (i = rg.Length) == 0)
        return String.Empty;

    if (i == 1)
        return rg[0];

    String s, t;
    int cch = 0;
    do
        cch += rg[--i].Length;
    while (i > 0);
    if (cch == 0)
        return String.Empty;

    i = rg.Length;
    fixed (Char* _p = (s = new String(default(Char), cch)))
    {
        Char* pDst = _p + cch;
        do
            if ((t = rg[--i]).Length > 0)
                fixed (Char* pSrc = t)
                    memcpy(pDst -= t.Length, pSrc, (UIntPtr)(t.Length << 1));
        while (pDst > _p);
    }
    return s;
}

[DllImport("MSVCR120_CLR0400", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void* memcpy(void* dest, void* src, UIntPtr cb);

Je dois mentionner que ce code a une légère modification par rapport à ce que j'utilise moi-même. Dans l'original, j'appelle l'instruction cpblk IL de C # pour faire la copie. Pour la simplicité et la portabilité dans le code ici, j'ai remplacé cela avec P / Invoke memcpy place, comme vous pouvez le voir. Pour des performances optimales sur x64 ( mais pas sur x86 ), vous pouvez utiliser la méthode cpblk à la place.