É String.Format tão eficiente quanto StringBuilder

160

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, por quê?

EDITAR

Após algumas respostas interessantes, percebi que provavelmente deveria ter sido um pouco mais claro no que estava perguntando. Eu não estava perguntando o que era mais rápido na concatenação de uma string, mas o que é mais rápido ao injetar uma string em outra.

Nos dois casos acima, quero injetar uma ou mais seqüências no meio de uma sequência de modelo predefinida.

Desculpe pela confusão

lomaxx
fonte
Deixe-os em aberto para permitir futuras melhorias.
Mark Biek
4
Em um cenário de caso especial, o mais rápido não é um desses: se a peça a ser substituída tiver tamanho igual à nova peça, você poderá alterar a sequência no local. Infelizmente, isso requer reflexão ou código inseguro e viola deliberadamente a imutabilidade da string. Não é uma boa prática, mas se a velocidade é um problema ... :)
Abel
no exemplo dado acima string s = "The "+cat+" in the hat";pode ser o mais rápido, a menos que seja usado em um loop; nesse caso, o mais rápido será com uma StringBuilder inicializada fora do loop.
Surya Pratap 24/09

Respostas:

146

NOTA: Esta resposta foi escrita quando o .NET 2.0 era a versão atual. Isso pode não se aplicar a versões posteriores.

String.Formatusa um StringBuilderinternamente:

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();
}

O código acima é um trecho do mscorlib, então a pergunta se torna "é StringBuilder.Append()mais rápida que StringBuilder.AppendFormat()"?

Sem benchmarking, eu provavelmente diria que o exemplo de código acima seria executado mais rapidamente .Append(). Mas é um palpite, tente fazer benchmarking e / ou criar um perfil dos dois para obter uma comparação adequada.

Este sujeito, Jerry Dixon, fez alguns testes comparativos:

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

Atualizada:

Infelizmente, o link acima morreu desde então. No entanto, ainda há uma cópia no Way Back Machine:

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

No final do dia, depende se a formatação de sua string será chamada repetidamente, ou seja, você está executando um processamento de texto sério com mais de cem megabytes de texto ou se está sendo chamado quando um usuário clica em um botão de vez em quando. A menos que você esteja executando um trabalho enorme de processamento em lote, eu continuaria com o String.Format, isso ajuda na legibilidade do código. Se você suspeitar de um gargalo de desempenho, cole um criador de perfil no seu código e veja onde ele realmente está.

Kev
fonte
8
Um problema com os benchmarks na página de Jerry Dixon é que ele nunca chama .ToString()o StringBuilderobjeto. Em muitas iterações, esse tempo faz uma grande diferença e significa que ele não está comparando maçãs com maçãs. Essa é a razão pela qual ele mostra um desempenho tão bom StringBuildere provavelmente explica sua surpresa. Acabei de repetir o benchmark corrigindo esse erro e obtive os resultados esperados: o String +operador foi mais rápido, seguido por StringBuilder, String.Formattrazendo a retaguarda.
Ben Collins
5
6 anos depois, isso não é mais tão. No Net4, string.Format () cria e armazena em cache uma instância StringBuilder que reutiliza, portanto, em alguns casos de teste, pode ser mais rápido que StringBuilder. Coloquei uma referência revisada na resposta abaixo (que ainda diz que o concat é mais rápido e, para o meu caso de teste, o formato é 10% mais lento que o StringBuilder).
Chris F Carroll
45

Na documentação do MSDN :

O desempenho de uma operação de concatenação para um objeto String ou StringBuilder depende da frequência com que ocorre uma alocação de memória. Uma operação de concatenação de String sempre aloca memória, enquanto uma operação de concatenação de StringBuilder somente aloca memória se o buffer do objeto StringBuilder for muito pequeno para acomodar os novos dados. Conseqüentemente, a classe String é preferível para uma operação de concatenação se um número fixo de objetos String for concatenado. Nesse caso, as operações de concatenação individuais podem até ser combinadas em uma única operação pelo compilador. Um objeto StringBuilder é preferível para uma operação de concatenação se um número arbitrário de seqüências de caracteres for concatenado; por exemplo, se um loop concatena um número aleatório de strings de entrada do usuário.

Greg
fonte
12

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

Portanto, se isso não é frequente, não importa. Mas se for uma operação comum, convém usar o primeiro método.

Vaibhav
fonte
10

Eu esperaria String.Format a ser mais lento - tem que analisar a cadeia e , em seguida, concatenar-lo.

Algumas notas:

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

Na maioria dos casos, acho que essa clareza, e não a eficiência, deve ser sua maior preocupação. A menos que você esteja esmagando toneladas de cordas ou construindo algo para um dispositivo móvel de baixa potência, isso provavelmente não prejudicará sua velocidade de execução.

Descobri que, nos casos em que estou criando cadeias de maneira bastante linear, fazer concatenações diretas ou usar o StringBuilder é a melhor opção. Eu sugiro isso nos casos em que a maioria da string que você está construindo é dinâmica. Como muito pouco do texto é estático, o mais importante é que fique claro onde cada parte do texto dinâmico está sendo colocada, caso seja necessário atualizar no futuro.

Por outro lado, se você está falando de um grande pedaço de texto estático com duas ou três variáveis, mesmo que seja um pouco menos eficiente, acho que a clareza que você obtém da 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 estiver em uma peça do que precisar atualizar três peças que você concatena.

saalon
fonte
Sim! Use String.Format quando fizer sentido, ou seja, quando estiver formatando cadeias. Use concatenação de seqüência de caracteres ou um StringBuilder ao executar concatenação mecânica. Sempre se esforce para escolher o método que comunica sua intenção ao próximo mantenedor.
23409 Rob
8

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 é o 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
  • string.Format: 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. O que para o meu dinheiro é frequente, 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);
Chris F Carroll
fonte
2
Por "string.Format não faz exatamente o que você pensa", quero dizer que no código-fonte 4.5 ele tenta criar e reutilizar uma instância StringBuilder em cache. Então, eu incluído essa abordagem no teste
Chris F Carroll
6

String.Format usa StringBuilderinternamente ... tão logicamente que leva à idéia de que seria um pouco menos eficiente devido a mais sobrecarga. No entanto, uma simples concatenação de strings é o método mais rápido de injetar uma string entre outras duas ... em um grau significativo. Essa evidência foi demonstrada por Rico Mariani em seu primeiro Teste de Desempenho, anos atrás. Fato simples é que as concatenações ... quando o número de partes da string é conhecido (sem limitação .. você pode concatenar mil partes ... desde que saiba sempre que sempre há 1000 partes) ... é sempre mais rápido que StringBuilderou String. Formato. Eles podem ser executados com uma única alocação de memória e uma série de cópias de memória. Aqui está a prova

E aqui está o código real para alguns métodos String.Concat, que chamam FillStringChecked, que usa ponteiros para copiar 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!";

Aproveitar!

jrista
fonte
no Net4, string.Format armazena em cache e reutiliza uma instância StringBuilder, portanto, em alguns usos, pode ser mais rápido.
Chris F Carroll
3

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

string cat = "cat";
string s = "The " + cat + " in the hat";
Vaibhav
fonte
não, a concatenação de strings é extremamente lenta, porque o .NET cria cópias extras de suas variáveis ​​de strings entre as operações de concat, neste caso: duas cópias extras mais a cópia final da atribuição. Resultado: desempenho extremamente ruim comparado ao StringBuilderque é feito para otimizar esse tipo de codificação em primeiro lugar.
Abel
Talvez seja o mais rápido a digitar;)
UpTheCreek
2
@Abel: A resposta pode estar faltando detalhes, mas essa abordagem é a opção mais rápida, neste exemplo em particular. O compilador transformará isso em uma única chamada String.Concat (); portanto, a substituição por um StringBuilder reduzirá a velocidade do código.
Dan C.
1
@ Vaibhav está correto: nesse caso, a concatenação é a mais rápida. Certamente, a diferença seria insignificante a menos que repetida muitas vezes, ou talvez operada sobre uma corda muito, muito maior.
21713 Ben Collins
0

Isso realmente depende. Para cadeias pequenas com poucas concatenações, é realmente mais rápido apenas anexá-las.

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

Mas para cadeias maiores (cadeias muito muito grandes), é mais eficiente usar o StringBuilder.

Joseph Daigle
fonte
0

Nos dois casos acima, quero injetar uma ou mais seqüências no meio de uma sequência de modelo predefinida.

Nesse caso, eu sugeriria que String.Format é o mais rápido porque é projetado para esse fim exato.

GateKiller
fonte
-1

Eu sugeriria que não, já que String.Format não foi projetado para concatenação, ele foi projetado para formatar a saída de várias entradas, como uma data.

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