special - use stringbuilder in c#




É String.Format tão eficiente quanto StringBuilder (8)

Suponha que eu tenha um construtor de string em C # que faça isso:

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

isso seria tão eficiente ou mais eficiente quanto ter:

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

Se sim, porque?

EDITAR

Depois de algumas respostas interessantes, percebi que provavelmente deveria ter sido um pouco mais claro no que estava perguntando. Eu não estava tanto perguntando qual era mais rápido em concatenar uma corda, mas o que é mais rápido em injetar uma corda na outra.

Nos dois casos acima eu quero injetar uma ou mais strings no meio de uma string de template predefinida.

Desculpe pela confusão


Nos dois casos acima eu quero injetar uma ou mais strings no meio de uma string de template predefinida.

Nesse caso, eu sugeriria String.Format é o mais rápido, porque é o design com esse objetivo exato.


Ah também, o mais rápido seria:

string cat = "cat";
string s = "The " + cat + " in the hat";

Eu acho que na maioria dos casos, como essa clareza e não a eficiência, deve ser sua maior preocupação. A menos que você esteja juntando toneladas de strings, ou construindo algo para um dispositivo móvel de baixa potência, isso provavelmente não afetará muito a sua velocidade de corrida.

Descobri que, nos casos em que estou construindo strings de maneira bastante linear, fazer concatenações diretas ou usar o StringBuilder é a melhor opção. Sugiro isso nos casos em que a maior parte da string que você está construindo é dinâmica. Como muito pouco do texto é estático, o mais importante é que fique claro onde cada texto dinâmico está sendo colocado, caso precise de atualizações no futuro.

Por outro lado, se você está falando sobre um grande pedaço de texto estático com duas ou três variáveis ​​nele, mesmo que seja um pouco menos eficiente, eu acho que a clareza que você ganha com o string.Format faz valer a pena. Eu usei isso no início desta semana quando tive que colocar um bit de texto dinâmico no centro de um documento de 4 páginas. Será mais fácil atualizar esse grande pedaço de texto se ele estiver em uma parte do que ter que atualizar três partes que você concatenar em conjunto.


Eu esperaria String.Format para ser mais lento - ele tem que analisar a seqüência de caracteres e, em seguida, concatená-lo.

Par de notas:

  • Formato é o caminho a percorrer para strings visíveis ao usuário em aplicativos profissionais; isso evita erros de localização
  • Se você souber o comprimento da string resultante de antemão, use o construtor StringBuilder (Int32) para predefinir a capacidade

Fiz alguns benchmarks de desempenho rápido e, para 100.000 operações com média de 10 execuções, o primeiro método (String Builder) ocupa quase metade do tempo do segundo (String Format).

Então, se isso for pouco frequente, não importa. Mas se é uma operação comum, então você pode querer usar o primeiro método.



Se apenas porque string.Format não faz exatamente o que você imagina, aqui está uma reprise dos testes 6 anos depois no Net45.

Concat ainda é mais rápido, mas na verdade é menos de 30% de diferença. StringBuilder e Format diferem em apenas 5-10%. Eu tenho variações de 20% executando os testes algumas vezes.

Milissegundos, um milhão de iterações:

  • Concatenação: 367
  • Novo stringBuilder para cada chave: 452
  • StringBuilder em cache: 419
  • Corda.Formatório: 475

A lição que retiro é que a diferença de desempenho é trivial e, portanto, não deve impedi-lo de escrever o código legível mais simples possível. Para o meu dinheiro, muitas vezes, mas nem sempre é 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);

String.Format usa StringBuilder internamente ... então, logicamente, isso leva à ideia de que seria um pouco menos eficiente devido a mais sobrecarga. No entanto, uma concatenação de strings simples é o método mais rápido de injetar uma string entre duas outras ... em um grau significativo. Esta evidência foi demonstrada por Rico Mariani em seu primeiro Quiz de Performance, anos atrás. Fato simples é que concatenações ... quando o número de partes de string é conhecido (sem limitação ... você poderia concatenar mil partes ... contanto que você saiba que são sempre 1000 partes) ... são sempre mais rápidas que StringBuilder ou String .Formato. Eles podem ser executados com uma única alocação de memória e uma série de cópias de memória. Here está a prova

E aqui está o código real para alguns métodos String.Concat, que no final das contas chamam FillStringChecked que usa ponteiros para copiar a memória (extraída 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);
        }
    }
}

Então:

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

Apreciar!







string.format