Pergunta

Fiquei me perguntando sobre o StringBuilder e tenho uma pergunta que esperava que a comunidade pudesse explicar.

Vamos esquecer a legibilidade do código, qual destes é mais rápido e porque?

StringBuilder.Append:

StringBuilder sb = new StringBuilder();
sb.Append(string1);
sb.Append("----");
sb.Append(string2);

StringBuilder.AppendFormat:

StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}----{1}",string1,string2);
Foi útil?

Solução

É impossível dizer, sem saber o tamanho de string1 e string2.

Com a chamada para AppendFormat, ele pré -alocará o buffer apenas uma vez, com o comprimento da corda do formato e as strings que serão inseridas e, em seguida, concatenam tudo e inserirão -o no buffer. Para cordas muito grandes, isso será vantajoso em relação às chamadas separadas para Append o que pode fazer com que o buffer se expanda várias vezes.

No entanto, as três chamadas para Append Pode ou não acionar o crescimento do buffer e essa verificação é executada a cada chamada. Se as cordas forem pequenas o suficiente e nenhuma expansão de buffer for acionada, será mais rápido que a chamada para AppendFormat Porque não terá que analisar a sequência do formato para descobrir onde fazer as substituições.

Mais dados são necessários para uma resposta definitiva

Deve -se notar que há pouca discussão sobre o uso da estática Concat Método no String classe (Resposta de Jon usando AppendWithCapacity me lembrou disso). Seus resultados de teste mostram que, para ser o melhor caso (supondo que você não precise tirar proveito do especificador de formato específico). String.Concat faz a mesma coisa, pois predeterminará o comprimento das cordas para concatenar e pré -alocar o buffer (com um pouco mais de sobrecarga devido a construções de loop através dos parâmetros). Seu desempenho será comparável ao de Jon AppendWithCapacity método.

Ou apenas o operador de adição simples, pois compila a uma chamada para String.Concat De qualquer forma, com a ressalva de que todas as adições estão na mesma expressão:

// One call to String.Concat.
string result = a + b + c;

NÃO

// Two calls to String.Concat.
string result = a + b;
result = result + c;

Para todos aqueles que colocam código de teste

Você precisa executar seus casos de teste em separado Executa (ou, pelo menos, execute um GC entre a medição de execuções de teste separadas). A razão para isso é que, se você disser, 1.000.000 de corridas, criando um novo StringBuilder em cada iteração do loop para um teste e você executa o próximo teste que faz o mesmo número de vezes, criando um adicional 1,000,000 StringBuilder Instâncias, o GC provavelmente intervirá durante o segundo teste e impedirá seu tempo.

Outras dicas

Casperone está correto. Depois de atingir um certo limiar, o Append() o método se torna mais lento do que AppendFormat(). Aqui estão os diferentes comprimentos e carrapatos decorridos de 100.000 iterações de cada método:

Comprimento: 1

Append()       - 50900
AppendFormat() - 126826

Comprimento: 1000

Append()       - 1241938
AppendFormat() - 1337396

Comprimento: 10.000

Append()       - 12482051
AppendFormat() - 12740862

Comprimento: 20.000

Append()       - 61029875
AppendFormat() - 60483914

Quando as cordas com um comprimento perto de 20.000 são introduzidas, o AppendFormat() função will um pouco superar Append().

Por que isso acontece? Ver Resposta de Casperone.

Editar:

Remontei cada teste individualmente em configuração de liberação e atualizei os resultados.

Casperone é totalmente preciso que depende dos dados. No entanto, suponha que você esteja escrevendo isso como uma biblioteca de classes para a terceira partes consumir - qual você usaria?

Uma opção seria obter o melhor dos dois mundos - calcule quantos dados você realmente terá que anexar e depois usar Stringbuilder.ensureCapacity Para garantir que precisamos apenas de um único buffer redimensionado.

Se eu não fosse também incomodado porém, eu usaria Append X3 - Parece "mais provável" ser mais rápido, pois analisar os tokens de formato de string em cada chamada é claramente o trabalho.

Observe que pedi à equipe da BCL uma espécie de "formatador em cache" que poderíamos criar usando uma string de formato e depois reutiliza repetidamente. É uma loucura que a estrutura precise analisar a sequência do formato cada vez que é usada.

EDIT: Ok, eu editei o código de John para flexibilidade e adicionei um "AppendWithCapacity", que apenas funciona pela capacidade necessária primeiro. Aqui estão os resultados para os diferentes comprimentos - para o comprimento 1, usei 1.000.000 iterações; Para todos os outros comprimentos, usei 100.000. (Isso foi apenas para obter tempos de execução sensatos.) Todos os momentos estão em milis.

Infelizmente, as tabelas realmente não funcionam. Os comprimentos eram 1, 1000, 10000, 20000

Vezes:

  • Anexar: 162, 475, 7997, 17970
  • AppendFormat: 392, 499, 8541, 18993
  • AppendWithCapacity: 139, 189, 1558, 3085

Então, por acaso, nunca vi AppendFormat Beat Append - mas eu fez Consulte ApêndwithCapacity vitória por uma margem muito substancial.

Aqui está o código completo:

using System;
using System.Diagnostics;
using System.Text;

public class StringBuilderTest
{            
    static void Append(string string1, string string2)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(string1);
        sb.Append("----");
        sb.Append(string2);
    }

    static void AppendWithCapacity(string string1, string string2)
    {
        int capacity = string1.Length + string2.Length + 4;
        StringBuilder sb = new StringBuilder(capacity);
        sb.Append(string1);
        sb.Append("----");
        sb.Append(string2);
    }

    static void AppendFormat(string string1, string string2)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("{0}----{1}", string1, string2);
    }

    static void Main(string[] args)
    {
        int size = int.Parse(args[0]);
        int iterations = int.Parse(args[1]);
        string method = args[2];

        Action<string,string> action;
        switch (method)
        {
            case "Append": action = Append; break;
            case "AppendWithCapacity": action = AppendWithCapacity; break;
            case "AppendFormat": action = AppendFormat; break;
            default: throw new ArgumentException();
        }

        string string1 = new string('x', size);
        string string2 = new string('y', size);

        // Make sure it's JITted
        action(string1, string2);
        GC.Collect();

        Stopwatch sw = Stopwatch.StartNew();
        for (int i=0; i < iterations; i++)
        {
            action(string1, string2);
        }
        sw.Stop();
        Console.WriteLine("Time: {0}ms", (int) sw.ElapsedMilliseconds);
    }
}

Append será mais rápido na maioria dos casos, porque existem muitas sobrecargas nesse método que permitem ao compilador chamar o método correto. Já que você está usando Strings a StringBuilder pode usar o String sobrecarga para Append.

AppendFormat Leva um String e então um Object[] o que significa que o formato terá que ser analisado e cada Object na matriz terá que ser ToString'd antes que possa ser adicionado ao StringBuilder's matriz interna.

Observação: Para o ponto de Casperone - é difícil dar uma resposta exata sem mais dados.

StringBuilder Também tem anexos em cascata: Append() retorna o StringBuilder por si só, para que você possa escrever seu código como este:

StringBuilder sb = new StringBuilder();
sb.Append(string1)
  .Append("----")
  .Append(string2);

Limpo e gera menos código de IL (embora isso seja realmente uma micro otimização).

Obviamente, perfil para saber com certeza em cada caso.

Dito isto, acho que em geral será o primeiro, porque você não está repetidamente analisando a corda do formato.

No entanto, a diferença seria muito pequena. A ponto de você realmente considerar usar AppendFormat Na maioria dos casos, de qualquer maneira.

Eu suponho que foi a ligação que fez a menor quantidade de trabalho. Anexar apenas as strings, onde o AppendFormat está fazendo substituições de strings. Claro que hoje em dia você nunca pode dizer ...

1 deve ser mais rápido porque está simplesmente anexando as strings, enquanto 2 deve criar uma string com base em um formato e depois anexar a sequência. Portanto, há um passo extra lá dentro.

Mais rápido é 1 no seu caso, no entanto, não é uma comparação justa. Você deve pedir a StringBuilder.appendFformat () vs StringBuilder.append (String.Format ()) - onde o primeiro é mais rápido devido ao trabalho interno com a matriz CHAR.

Sua segunda opção é mais legível.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top