StringBuilder를 사용하여 메소드 제거 메소드를 루프에서 새 StringBuilder를 만드는 것보다 메모리 효율적입니까?

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

문제

C #에서 더 메모리 효율적인 기능 : 옵션 #1 또는 옵션 #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);
}
도움이 되었습니까?

해결책

몇 가지 답변은 내가 내 더프에서 내려서 직접 알아내는 것이 내 결과입니다. 나는 감정이 일반적 으로이 사이트의 곡물에 반대한다고 생각하지만, 당신이 올바른 일을 원한다면, 당신도 할 수도 있습니다 ... :)

옵션 #1을 수정하여 @ty 제안을 활용하여 stringbuilder.length = 0을 제거하여 제거 메소드를 사용합니다. 이것은 두 옵션의 코드를 더 유사하게 만들었습니다. 두 가지 차이점은 이제 StringBuilder의 생성자가 루프 안팎에 있는지 여부이며 옵션 #1은 이제 길이 방법을 사용하여 StringBuilder를 제거합니다. 두 옵션 모두 가비지 수집기가 약간의 작업을 수행하도록하기 위해 100,000 요소가있는 출력 스트링 어레이를 통해 실행되도록 설정되었습니다.

몇 가지 답변은 다양한 Perfmon 카운터 및 그러한 것을보고 결과를 사용하여 옵션을 선택할 힌트를 제공했습니다. 나는 약간의 연구를 수행하고 직장에서 가지고있는 Visual Studio Team Systems 개발자 에디션의 내장 성능 탐색기를 사용했습니다. 나는 그것을 설정하는 방법을 설명하는 멀티 파트 시리즈의 두 번째 블로그 항목을 찾았습니다. 여기. 기본적으로, 당신은 단위 테스트를 통해 프로파일을 프로파일 링하려는 코드를 가리 키게합니다. 마법사와 일부 구성을 통과하십시오. 단위 테스트 프로파일 링을 시작하십시오. .NET 객체 할당 및 수명 메트릭을 활성화했습니다. 이 답변을 위해 포맷하기 어려운 프로파일 링의 결과는 끝에 배치했습니다. 텍스트를 복사하여 엑셀에 붙여 넣고 약간 마사지하면 읽을 수 있습니다.

옵션 #1은 쓰레기 수집기가 작업을 조금 덜 작업하고 옵션 #2보다 StringBuilder 객체에 메모리와 인스턴스의 절반을 할당하기 때문에 가장 메모리 효율입니다. 일상적인 코딩의 경우 옵션 #2는 완벽하게 괜찮습니다.

아직도 읽고 있다면 옵션 #2가 경험 C/C ++ 개발자가 탄도에 대한 메모리 누출 감지기를 만들기 때문에이 질문을했습니다. StringBuilder 인스턴스가 재 할리되기 전에 해제되지 않으면 막대한 메모리 누출이 발생합니다. 물론, 우리는 C# 개발자들이 그런 것들에 대해 걱정하지 않습니다 (그들이 우리를 뛰어 넘고 물 때까지). 모두에게 감사합니다 !!


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

다른 팁

옵션 2는 실제로 옵션을 능가해야합니다. 1. 전화 행위 Remove "강제"StringBuilder가 이미 반환 된 문자열의 사본을 가져 가도록합니다. 문자열은 실제로 StringBuilder 내에서 변이 가능하며 StringBuilder는 필요하지 않으면 사본을 가져 가지 않습니다. 옵션 1을 사용하면 기본적으로 배열을 지우기 전에 복사합니다 - 옵션 2와 함께 사본이 필요하지 않습니다.

옵션 2의 유일한 단점은 문자열이 길어지면 추가로 여러 개의 사본이 있어야한다는 것입니다. 반면 옵션 1은 원래 버퍼의 크기를 유지합니다. 그러나 이것이 사실이라면 추가 복사를 피하기위한 초기 용량을 지정하십시오. (샘플 코드에서 문자열은 기본 16 자보다 커집니다. 예를 들어, 32의 용량으로 초기화하는 추가 문자열이 줄어 듭니다.)

그러나 성능 외에도 옵션 2는 더 깨끗합니다.

프로파일 링하는 동안 루프에 들어가면 StringBuilder의 길이를 0으로 설정할 수도 있습니다.

formattedOutput.Length = 0;

당신은 기억에만 관심이 있기 때문에 다음을 제안합니다.

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

이름이 지정된 변수는 원래 구현에서 동일한 크기이지만 다른 객체는 필요하지 않습니다. StringBuilder는 내부적으로 문자열 및 기타 객체를 사용하며 GC'D가 필요한 많은 객체를 만들게됩니다.

옵션 1의 라인 :

string output = formattedOutput.ToString();

그리고 옵션 2의 라인 :

ExistingOutputMethodThatOnlyTakesAString(
           formattedOutputInsideALoop.ToString());

an을 만들 것입니다 불변 접두사 + outputString + postfix의 값을 가진 객체. 이 문자열은 어떻게 만들 든 크기가 동일합니다. 당신이 실제로 묻는 것은 더 메모리 효율적인 것입니다.

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

또는

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

StringBuilder를 완전히 건너 뛰는 것은 위의 것보다 더 메모리 효율적입니다.

응용 프로그램에서 두 가지 중 어느 것이 더 효율적인지 알아야한다면 (이것은 목록의 크기, 접두사 및 출력 스트링에 따라 다를 수 있습니다) Red-Gate Ants Profiler를 권장합니다. http://www.red-gate.com/products/ants_profiler/index.htm

제이슨

말하기는 싫지만 테스트하는 것은 어떻습니까?

이 물건은 혼자서 쉽게 알 수 있습니다. perfmon.exe를 실행하고 .net memory + gen 0 컬렉션에 대한 카운터를 추가하십시오. 테스트 코드를 백만 번 실행하십시오. 옵션 #1에는 컬렉션 옵션 #2 요구의 절반이 필요합니다.

우리는 Java와 함께 전에 이것에 대해 이야기했습니다, C# 버전의 [릴리스] 결과는 다음과 같습니다.

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

업데이트 : 비 과학적 분석에서 Perfmon의 모든 메모리 성능 카운터를 모니터링하면서 두 가지 방법을 실행할 수있는 두 가지 방법은 두 가지 방법과 어떤 종류의 식별 할 수없는 차이를 초래하지 않았습니다 (테스트가 실행되는 동안 일부 카운터가 스파이크하는 것 외에는).

그리고 여기 내가 테스트했던 것입니다.

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
    }
} 

옵션 1이 시나리오에서는 옵션 2가 읽고 유지하기가 더 쉽지만이 시나리오에서는 약간 빠릅니다. 이 작업을 수백만 번 연속적으로 연속적으로 수행하지 않는 한 옵션 1과 2가 단일 반복에서 실행할 때 거의 동일하다고 생각하기 때문에 옵션 2를 고수합니다.

확실히 더 간단하다면 옵션 #2라고 말하고 싶습니다. 성능 측면에서 테스트하고 볼 필요가있는 것 같습니다. 덜 간단한 옵션을 선택하기에 충분한 차이가 없다고 생각합니다.

옵션 1이 약간 더 많을 것이라고 생각합니다 메모리 새로운 객체로서 효율적으로마다 생성되는 것은 아닙니다. 그러나 GC는 옵션 2에서와 같이 자원을 정리하는 데 꽤 좋은 일을합니다.

나는 당신이 조기 최적화의 함정에 빠지고 있다고 생각합니다 (모든 악의 뿌리 -Knuth). IO는 String Builder보다 훨씬 더 많은 리소스를 취할 것입니다.

이 경우 더 명확하고 클리너 옵션으로 이동합니다.이 경우 옵션 2.

  1. 측정하십시오
  2. 필요한 메모리에 가능한 한 가깝게 사전 할당 할 수 있습니다.
  3. 속도가 선호하는 경우 상당히 간단한 다중 스레드 전면에서 중간, 중간에서 종료 동시 접근 방식을 고려하십시오 (필요에 따라 노동의 확장).
  4. 다시 측정하십시오

당신에게 더 중요한 것은 무엇입니까?

  1. 메모리

  2. 속도

  3. 명쾌함

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top