StringBuilder만큼 효율적인 String.Format


160

이 작업을 수행하는 C #에 stringbuilder가 있다고 가정하십시오.

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

그것은 다음과 같이 효율적이거나 더 효율적일 것입니다.

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

그렇다면 왜 그렇습니까?

편집하다

몇 가지 흥미로운 대답을 한 후에, 나는 내가 요구 한 것에 대해 좀 더 명확 해졌을 것임을 깨달았습니다. 나는 문자열을 연결하는 데 더 빠르지는 않았지만 한 문자열을 다른 문자열 에 주입 하는 것이 더 빠릅니다 .

위의 두 경우 모두 미리 정의 된 템플릿 문자열의 중간에 하나 이상의 문자열을 삽입하려고합니다.

혼란을 드려 죄송합니다


나중에 개선 할 수 있도록 열어 두십시오.
Mark Biek

4
특수한 경우 시나리오가 가장 빠르지 않습니다. 교체 할 부품의 크기가 새 부품과 같으면 문자열을 내부에서 변경할 수 있습니다. 불행히도, 이것은 리플렉션 또는 안전하지 않은 코드를 요구하며 의도적으로 문자열의 불변성을 위반합니다. 좋은 습관은 아니지만 속도가 문제라면 ... :)
Abel

string s = "The "+cat+" in the hat";루프에서 사용하지 않는 한 위의 예제 에서 가장 빠를 수 있습니다.이 경우 가장 빠른 StringBuilder 루프 외부에서 초기화됩니다.
Surya Pratap

답변:


146

참고 : 이 답변은 .NET 2.0이 현재 버전 일 때 작성되었습니다. 이후 버전에는 더 이상 적용되지 않을 수 있습니다.

String.FormatStringBuilder내부적으로 사용합니다 :

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

위 코드는 mscorlib의 스 니펫이므로 질문이 " StringBuilder.Append()보다 빠릅니다 StringBuilder.AppendFormat()"?

벤치마킹하지 않으면 위의 코드 샘플이을 사용하여 더 빨리 실행될 것이라고 말할 수 .Append()있습니다. 그러나 적절한 비교를 위해 벤치마킹 및 / 또는 프로파일 링을 시도해보십시오.

이 챕터 Jerry Dixon은 벤치마킹을 수행했습니다.

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

업데이트 :

슬프게도 위의 링크는 그 후 죽었습니다. 그러나 Way Back Machine에는 여전히 사본이 있습니다.

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

하루가 끝나면 문자열 형식이 반복적으로 호출 될지 여부, 즉 100 메가 바이트 이상의 텍스트를 처리하는 심각한 텍스트 처리를 수행하는지 또는 사용자가 몇 번이고 버튼을 클릭 할 때 호출되는지 여부에 따라 다릅니다. String.Format을 고수하는 거대한 일괄 처리 작업을 수행하지 않으면 코드 가독성이 향상됩니다. 성능 병목 현상이 의심되면 코드에 프로파일 러를 붙이고 실제 위치를 확인하십시오.


8
제리 딕슨의 페이지의 벤치 마크 하나의 문제는 그가 결코 호출하지 않는다는 것이다 .ToString()상의 StringBuilder객체입니다. 많은 반복에서, 그 시간은 큰 차이를 만듭니다. 그리고 그는 사과와 사과를 비교하지 않고 있음을 의미합니다. 그것이 그가 훌륭한 성과를 보여 StringBuilder주었고 아마도 그의 놀람을 설명 하는 이유 입니다. 난 그냥 그런 실수를 정정 벤치 마크를 반복하고 예상 결과를 가지고 다음 String +연산자는 빠르고 뒤를이었다 StringBuilderString.Format후면을 데리고.
벤 콜린스

5
6 년 후, 이것은 더 이상 그렇게되지 않습니다. Net4에서 string.Format ()은 재사용하는 StringBuilder 인스턴스를 생성하고 캐시하므로 일부 테스트 경우 StringBuilder보다 빠를 수 있습니다. 아래 답변에 수정 된 벤치 마크를 넣었습니다 (여전히 concat이 가장 빠르며 테스트 사례의 경우 형식이 StringBuilder보다 10 % 느립니다).
Chris F Carroll

45

로부터 MSDN 문서 :

String 또는 StringBuilder 객체에 대한 연결 작업의 성능은 메모리 할당 빈도에 따라 다릅니다. 문자열 연결 작업은 항상 메모리를 할당하지만 StringBuilder 연결 작업은 StringBuilder 객체 버퍼가 너무 작아서 새 데이터를 수용 할 수없는 경우에만 메모리를 할당합니다. 따라서 고정 된 수의 String 객체가 연결된 경우 연결 작업에 String 클래스가 선호됩니다. 이 경우 컴파일러에서 개별 연결 작업을 단일 작업으로 결합 할 수도 있습니다. 임의의 수의 문자열이 연결된 경우 연결 작업에 StringBuilder 객체가 선호됩니다. 예를 들어, 루프가 임의의 수의 사용자 입력 문자열을 연결하는 경우.


12

몇 가지 빠른 성능 벤치 마크를 실행했으며 평균 10 회 실행에서 100,000 건의 작업에 대해 첫 번째 방법 (문자열 작성기)이 두 번째 (문자열 형식) 시간의 거의 절반을 차지합니다.

따라서 드물게 발생하더라도 중요하지 않습니다. 그러나 일반적인 작업 인 경우 첫 번째 방법을 사용할 수 있습니다.


10

String.Format 이 느릴 것으로 예상 합니다. 문자열을 구문 분석 한 다음 연결해야합니다.

몇 가지 메모 :

  • 형식 은 전문 응용 프로그램에서 사용자가 볼 수있는 문자열을 찾는 방법입니다. 이것은 현지화 버그를 피합니다
  • 결과 문자열의 길이를 미리 알고 있으면 StringBuilder (Int32) 생성자를 사용하여 용량을 미리 정의하십시오.

8

대부분의 경우 효율성이 아닌 이러한 명확성과 같은 것이 가장 큰 관심사라고 생각합니다. 수많은 줄을 뭉개거나 저전력 모바일 장치를 위해 무언가를 짓지 않는 한, 이것은 달리기 속도에 흠집을 내지 않을 것입니다.

상당히 선형적인 방식으로 문자열을 작성하는 경우 직선 연결을 수행하거나 StringBuilder를 사용하는 것이 최선의 선택이라는 것을 알았습니다. 당신이 만들고있는 대부분의 문자열이 역동적 인 경우에 이것을 제안합니다. 정적 텍스트가 거의 없기 때문에 가장 중요한 것은 나중에 동적 텍스트를 업데이트해야 할 경우를 대비하여 각 동적 텍스트를 어디에 배치 할 것인지가 분명하다는 것입니다.

반면에 두 개 또는 세 개의 변수가있는 정적 텍스트의 큰 덩어리에 대해 이야기하는 경우 조금 덜 효율적이더라도 문자열에서 얻을 수있는 선명도를 생각합니다. 나는 이번 주 초에 4 페이지 문서의 중앙에 1 비트의 동적 텍스트를 배치해야 할 때 이것을 사용했습니다. 함께 연결되는 세 개의 조각을 업데이트하는 것보다 큰 텍스트 조각을 한 조각으로 업데이트하는 것이 더 쉽습니다.


예! String.Format을 사용하는 것이 합리적 일 때, 즉 문자열을 포맷 할 때 사용하십시오. 기계적 연결을 수행 할 때는 문자열 연결 또는 StringBuilder를 사용하십시오. 항상 다음 관리자에게 의사를 전달하는 방법을 선택하도록 노력하십시오.
Rob

8

string.Format이 생각한대로 정확하게 수행하지 않기 때문에 6 년 후 Net45에서 테스트를 다시 실행합니다.

Concat은 여전히 ​​가장 빠르지 만 실제로는 30 % 미만입니다. StringBuilder와 형식은 5-10 % 정도 차이가 있습니다. 테스트를 몇 번 실행하는 20 %의 변형이있었습니다.

밀리 초, 백만 회 반복 :

  • 연결 : 367
  • 각 키에 대한 새 stringBuilder : 452
  • 캐시 된 StringBuilder : 419
  • 문자열 형식 : 475

내가 얻은 교훈은 성능 차이가 사소한 것이므로 가능한 가장 간단한 읽을 수있는 코드 작성을 중단해서는 안됩니다. 내 돈을 위해 종종 항상 그런 것은 아닙니다 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);

2
"string.Format은 여러분이 생각하는대로 정확하게 행동하지 않습니다"라는 말은 4.5 소스 코드에서 캐시 된 StringBuilder 인스턴스를 생성하고 재사용하려고 시도한다는 것을 의미합니다. 그래서이 접근법을 테스트에 포함 시켰습니다
Chris F Carroll

6

String.Format은 StringBuilder내부적으로 사용 하기 때문에 논리적으로 더 많은 오버 헤드로 인해 성능이 약간 떨어질 수 있습니다. 그러나 간단한 문자열 연결은 두 문자열 사이에 하나의 문자열을 주입하는 가장 빠른 방법입니다 ... 이 증거는 몇 년 전 그의 첫 번째 성능 퀴즈에서 Rico Mariani에 의해 입증되었습니다. 간단한 사실은 연결 부분입니다 ... 제한없이 문자열 부분의 수를 알 때 (제한없이 .1000 개의 부분을 연결할 수 있습니다 ... 항상 1000 부분을 아는 한) ... 항상 StringBuilder또는 문자열 보다 빠릅니다 . 체재. 단일 메모리 할당으로 일련의 메모리 사본을 수행 할 수 있습니다. 여기 증거가 있습니다

다음은 String.Concat 메소드의 실제 코드입니다. 궁극적으로 FillStringChecked를 호출하여 포인터를 사용하여 메모리를 복사합니다 (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);
        }
    }
}

그럼:

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

즐겨!


Net4에서 string.Format은 StringBuilder 인스턴스를 캐시하고 재사용하므로 일부 사용에서는 더 빠를 수 있습니다.
Chris F Carroll

3

또한 가장 빠른 것은 다음과 같습니다.

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

아니요. .NET은 연결 작업간에 문자열 변수의 추가 복사본을 생성하므로이 경우 문자열 연결이 매우 느립니다. 결과 : StringBuilder이 유형의 코딩을 우선적으로 최적화하는 데 비해 성능이 매우 떨어 집니다.
Abel

아마 가장 빠름);)
UpTheCreek

2
@ Abel : 대답에는 세부 사항이 부족할 수 있지만이 방법에서는이 방법이 가장 빠른 옵션입니다. 컴파일러는 이것을 단일 String.Concat () 호출로 변환하므로 StringBuilder로 바꾸면 실제로 코드 속도가 느려집니다.
Dan C.

1
@Vaibhav가 정확합니다.이 경우 연결이 가장 빠릅니다. 물론, 여러 번 반복하지 않거나 훨씬 더 큰 줄에서 작동하지 않으면 차이가 중요하지 않습니다.
벤 콜린스

0

정말 달려 있습니다. 연결이 적은 작은 문자열의 경우 실제로 문자열을 추가하는 것이 더 빠릅니다.

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

그러나 더 큰 문자열 (매우 큰 문자열)의 경우 StringBuilder를 사용하는 것이 더 효율적입니다.


0

위의 두 경우 모두 미리 정의 된 템플릿 문자열의 중간에 하나 이상의 문자열을 삽입하려고합니다.

이 경우 String.Format이 정확한 목적을 위해 디자인되었으므로 가장 빠릅니다.



-1

String.Format은 연결을 위해 설계되지 않았기 때문에 날짜와 같은 다양한 입력의 출력을 형식화하기위한 디자인이었습니다.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.