Está usando StringBuilder Remove método mais eficiente de memória do que criar um novo StringBuilder em loop?

StackOverflow https://stackoverflow.com/questions/266923

Pergunta

Em C #, que é mais eficiente da memória:? Opção 1 ou Opção # 2

public void TestStringBuilder()
{
    //potentially a collection with several hundred items:
    string[] outputStrings = new string[] { "test1", "test2", "test3" };

    //Option #1
    StringBuilder formattedOutput = new StringBuilder();
    foreach (string outputString in outputStrings)
    {
        formattedOutput.Append("prefix ");
        formattedOutput.Append(outputString);
        formattedOutput.Append(" postfix");

        string output = formattedOutput.ToString();
        ExistingOutputMethodThatOnlyTakesAString(output);

        //Clear existing string to make ready for next iteration:
        formattedOutput.Remove(0, output.Length);
    }

    //Option #2
    foreach (string outputString in outputStrings)
    {
        StringBuilder formattedOutputInsideALoop = new StringBuilder();

        formattedOutputInsideALoop.Append("prefix ");
        formattedOutputInsideALoop.Append(outputString);
        formattedOutputInsideALoop.Append(" postfix");

        ExistingOutputMethodThatOnlyTakesAString(
           formattedOutputInsideALoop.ToString());
    }
}

private void ExistingOutputMethodThatOnlyTakesAString(string output)
{
    //This method actually writes out to a file.
    System.Console.WriteLine(output);
}
Foi útil?

Solução

Várias das respostas sugeriu gentilmente que eu saia do meu duff e descobrir por mim mesmo assim abaixo estão os meus resultados. Eu acho que esse sentimento geralmente vai na contramão deste site, mas se você quer algo bem feito, assim como você pode fazer ....:)

Eu modifiquei a opção # 1 para tirar vantagem da sugestão @Ty usar StringBuilder.Length = 0 em vez do método Remove. Isso fez com que o código das duas opções mais similares. As duas diferenças são agora se o construtor para o StringBuilder está dentro ou fora do circuito e uma opção # agora usa o método de Comprimento para limpar o StringBuilder. Ambas as opções foram criadas para executar mais de um array outputStrings com 100.000 elementos para fazer o coletor de lixo fazer algum trabalho.

casal responde a uma oferecidos dicas para olhar para os vários contadores de desempenho e tal e usar os resultados para escolher uma opção. Fiz algumas pesquisas e acabou usando o built-in Performance Explorer do Visual Studio Team Sistemas edição desenvolvedor que tenho no trabalho. Eu encontrei a segunda entrada do blog de uma série com várias partes que explica como configurá-lo aqui . Basicamente, você conectar um teste de unidade para apontar para o código que você deseja perfil; passar por um assistente e algumas configurações; e lançar o perfil de teste de unidade. I permitiu a alocação & vitalícias métricas .NET objeto. Os resultados do profiling onde difíceis de formatar para esta resposta assim que eu coloquei no final. Se você copiar e colar o texto em Excel e massageá-los um pouco, eles vão ser legível.

Opção # 1 é a eficiência mais memória, porque torna o coletor de lixo fazer um pouco menos trabalho e ele aloca metade da memória e instâncias para o objeto StringBuilder de Opção # 2. Para todos os dias de codificação, escolhendo a opção # 2 é perfeitamente bem.

Se você ainda está lendo, eu fiz esta pergunta porque Opção # 2 fará com que os detectores de vazamento de memória de uma experiência C / programador C ++ ir balísticos. Um vazamento de memória enorme ocorrerá se a instância StringBuilder não é liberado antes de ser transferido. Claro, nós C # desenvolvedores não se preocupe com essas coisas (até que saltar para cima e morder-nos). Obrigado a todos !!


ClassName   Instances   TotalBytesAllocated Gen0_InstancesCollected Gen0BytesCollected  Gen1InstancesCollected  Gen1BytesCollected
=======Option #1                    
System.Text.StringBuilder   100,001 2,000,020   100,016 2,000,320   2   40
System.String   301,020 32,587,168  201,147 11,165,268  3   246
System.Char[]   200,000 8,977,780   200,022 8,979,678   2   90
System.String[] 1   400,016 26  1,512   0   0
System.Int32    100,000 1,200,000   100,061 1,200,732   2   24
System.Object[] 100,000 2,000,000   100,070 2,004,092   2   40
======Option #2                 
System.Text.StringBuilder   200,000 4,000,000   200,011 4,000,220   4   80
System.String   401,018 37,587,036  301,127 16,164,318  3   214
System.Char[]   200,000 9,377,780   200,024 9,379,768   0   0
System.String[] 1   400,016 20  1,208   0   0
System.Int32    100,000 1,200,000   100,051 1,200,612   1   12
System.Object[] 100,000 2,000,000   100,058 2,003,004   1   20

Outras dicas

Opção 2 deve (creio eu) realmente opção outperform 1. O ato de chamar "forças" Remove o StringBuilder para tirar uma cópia da string já está retornado. A string é na verdade mutável dentro StringBuilder, e não StringBuilder não levar uma cópia a menos que ele precisa. Com a opção 1 copia antes basicamente limpar a matriz fora - com a opção 2 nenhuma cópia é necessária

.

A única desvantagem da opção 2 é que, se as extremidades da corda por ser longa, haverá várias cópias feitas enquanto anexando - enquanto a opção 1 mantém o tamanho original do buffer. Se isso vai ser o caso, no entanto, especificar uma capacidade inicial para evitar a cópia extra. (Em seu código de exemplo, a cadeia vai acabar sendo maior do que o padrão de 16 caracteres -. Inicializando-lo com uma capacidade de, digamos, 32 reduzirá as cordas extras necessárias)

Além do desempenho, no entanto, a opção 2 é apenas mais limpo.

Enquanto o perfilamento, você pode também tentar apenas definir o comprimento do StringBuilder a zero quando você entra no loop.

formattedOutput.Length = 0;

Uma vez que você está preocupado apenas com a memória eu sugiro:

foreach (string outputString in outputStrings)
    {    
        string output = "prefix " + outputString + " postfix";
        ExistingOutputMethodThatOnlyTakesAString(output)  
    }

A variável chamada de saída é do mesmo tamanho na sua aplicação original, mas não outros objetos são necessários. StringBuilder utiliza cadeias e outros objetos internamente e será criado muitos objetos que precisam ser GC'd.

Tanto a linha de opção 1:

string output = formattedOutput.ToString();

E a linha de opção 2:

ExistingOutputMethodThatOnlyTakesAString(
           formattedOutputInsideALoop.ToString());

criará um imutável objeto com o valor do prefixo + OutputString + postfix. Essa seqüência é do mesmo tamanho não importa como você criá-lo. O que você realmente está pedindo é que é mais eficiente da memória:

    StringBuilder formattedOutput = new StringBuilder(); 
    // create new string builder

ou

    formattedOutput.Remove(0, output.Length); 
    // reuse existing string builder

Ignorar o StringBuilder inteiramente será mais memória eficiente do que qualquer um dos acima.

Se você realmente precisa saber qual dos dois é mais eficiente na sua aplicação (isto irá provavelmente variar de acordo com o tamanho da sua lista, prefixo e outputStrings) Eu recomendaria-portão vermelho ANTS Profiler http://www.red-gate.com/products/ants_profiler/index.htm

Jason

odeio dizer isso, mas como sobre apenas testá-lo?

Este material é fácil de descobrir por si mesmo. Run Perfmon.exe e adicionar um contador para .NET Memória + Gen 0 Collections. Execute o código de teste de um milhão de vezes. Você verá que a opção # 1 requer metade do número de opção coleções # 2 necessidades.

Nós conversamos sobre isso antes com Java , aqui está os resultados [Release] da versão C #:

Option #1 (10000000 iterations): 11264ms
Option #2 (10000000 iterations): 12779ms

Update: Na minha análise não-científica permitindo que os dois métodos para executar enquanto monitora todo o desempenho da memória contadores em perfmon não resultou em qualquer tipo de diferença perceptível com um ou outro método (além de ter alguns contadores pico somente enquanto ambos os testes foi execução).

E aqui está o que eu usei para teste:

class Program
{
    const int __iterations = 10000000;

    static void Main(string[] args)
    {
        TestStringBuilder();
        Console.ReadLine();
    }

    public static void TestStringBuilder()
    {
        //potentially a collection with several hundred items:
        var outputStrings = new [] { "test1", "test2", "test3" };

        var stopWatch = new Stopwatch();

        //Option #1
        stopWatch.Start();
        var formattedOutput = new StringBuilder();

        for (var i = 0; i < __iterations; i++)
        {
            foreach (var outputString in outputStrings)
            {
                formattedOutput.Append("prefix ");
                formattedOutput.Append(outputString);
                formattedOutput.Append(" postfix");

                var output = formattedOutput.ToString();
                ExistingOutputMethodThatOnlyTakesAString(output);

                //Clear existing string to make ready for next iteration:
                formattedOutput.Remove(0, output.Length);
            }
        }
        stopWatch.Stop();

        Console.WriteLine("Option #1 ({1} iterations): {0}ms", stopWatch.ElapsedMilliseconds, __iterations);
            Console.ReadLine();
        stopWatch.Reset();

        //Option #2
        stopWatch.Start();
        for (var i = 0; i < __iterations; i++)
        {
            foreach (var outputString in outputStrings)
            {
                StringBuilder formattedOutputInsideALoop = new StringBuilder();

                formattedOutputInsideALoop.Append("prefix ");
                formattedOutputInsideALoop.Append(outputString);
                formattedOutputInsideALoop.Append(" postfix");

                ExistingOutputMethodThatOnlyTakesAString(
                   formattedOutputInsideALoop.ToString());
            }
        }
        stopWatch.Stop();

        Console.WriteLine("Option #2 ({1} iterations): {0}ms", stopWatch.ElapsedMilliseconds, __iterations);
    }

    private static void ExistingOutputMethodThatOnlyTakesAString(string s)
    {
        // do nothing
    }
} 

Opção 1 neste cenário é ligeiramente mais rápido do que a opção 2 é mais fácil de ler e manter. A menos que você acontecer estar realizando este milhões de operação de vezes volta para trás eu iria ficar com a opção 2, porque eu suspeito que a opção 1 e 2 são sobre o mesmo quando rodando em uma única iteração.

Eu diria que a opção # 2 se definitivamente mais simples. Em termos de desempenho, soa como algo que você só precisa de teste e veja. Eu acho que isso não faz diferença o suficiente para escolher a opção menos simples.

Eu acho que a opção 1 seria um pouco mais memória toda eficiente como um novo objeto não é criado. Dito isto, o GC faz um trabalho muito bom de limpeza de recursos como na opção 2.

Eu acho que você pode estar caindo na armadilha de otimização prematura ( a raiz de todo o mal - -Knuth). O seu IO vai ter muito mais recursos do que o construtor string.

Eu costumo ir com a opção mais claro / mais limpo, neste caso, a opção 2.

Rob

  1. Meça-
  2. Pré-alocar o mais próximo possível para a quantidade de memória que você acha que vai precisar
  3. Se a velocidade é a sua preferência, então considerar uma frente bastante para a frente de multi-threaded de média, média para acabar abordagem simultânea (expandir a divisão do trabalho, conforme exigido)
  4. medi-la novamente

o que é mais importante para você?

  1. memória

  2. velocidade

  3. clareza

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