문제

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

그렇다면 왜 그렇습니까?

편집하다

몇 가지 흥미로운 답변을 듣고 나서 나는 내가 질문한 내용을 좀 더 명확하게 설명했어야 했다는 것을 깨달았습니다.나는 문자열을 연결하는 데 어느 것이 더 빠른지, 어느 것이 더 빠른지 묻지 않았습니다. 주사 한 문자열을 다른 문자열로 연결합니다.

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

혼란을 드려 죄송합니다

도움이 되었습니까?

해결책

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

String.Format 사용하다 StringBuilder 내부적으로:

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의 일부이므로 질문은 "is"가 됩니다. 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

결국 문자열 형식이 반복적으로 호출되는지 여부에 따라 다릅니다.100MB가 넘는 텍스트를 처리하는 중인지, 사용자가 버튼을 클릭할 때 호출되는지 여부를 확인합니다.대규모 일괄 처리 작업을 수행하지 않는 한, 코드 가독성에 도움이 되는 String.Format을 사용하겠습니다.성능 병목 현상이 의심되는 경우 코드에 프로파일러를 적용하고 실제 위치를 확인하세요.

다른 팁

로부터 MSDN 문서:

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

몇 가지 빠른 성능 벤치마크를 실행했는데 10회 실행에 대한 평균 100,000번의 작업에 대해 첫 번째 방법(문자열 빌더)은 두 번째 방법(문자열 형식)에 비해 거의 절반의 시간이 소요되었습니다.

따라서 이것이 자주 발생하지 않더라도 문제가 되지 않습니다.하지만 일반적인 작업이라면 첫 번째 방법을 사용하는 것이 좋습니다.

나는 기대할 것이다 문자열.형식 속도가 느려지려면 문자열을 구문 분석해야 하고 그 다음에 그것을 연결하십시오.

몇 가지 메모:

  • 체재 전문 응용 프로그램에서 사용자가 볼 수 있는 문자열을 사용하는 방법입니다.이는 현지화 버그를 방지합니다.
  • 결과 문자열의 길이를 미리 알고 있는 경우 다음을 사용하십시오. 스트링빌더(Int32) 용량을 미리 정의하는 생성자

string.Format이 여러분이 생각하는 것과 정확히 일치하지 않기 때문에 6년 후 Net45에서 테스트를 다시 실행해 보겠습니다.

Concat은 여전히 ​​가장 빠르지만 실제로는 30% 미만의 차이가 있습니다.StringBuilder와 Format은 거의 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);

대부분의 경우 효율성이 아닌 이러한 명확성이 가장 큰 관심사가 되어야 한다고 생각합니다.수많은 줄을 뭉개거나 저전력 모바일 장치용으로 무언가를 만드는 것이 아니라면 실행 속도에 큰 영향을 미치지 않을 것입니다.

나는 상당히 선형적인 방식으로 문자열을 작성하는 경우 직선 연결을 수행하거나 StringBuilder를 사용하는 것이 최선의 선택이라는 것을 발견했습니다.작성 중인 문자열의 대부분이 동적인 경우에 이 방법을 제안합니다.정적 텍스트는 거의 없기 때문에 가장 중요한 점은 향후 업데이트가 필요한 경우를 대비해 동적 텍스트의 각 부분이 어디에 배치되는지 명확하다는 것입니다.

반면에 2~3개의 변수가 포함된 큰 덩어리의 정적 텍스트에 대해 이야기하는 경우 효율성이 조금 떨어지더라도 string.Format을 통해 얻을 수 있는 명확성은 그만한 가치가 있다고 생각합니다.저는 이번 주 초에 4페이지짜리 문서 중앙에 동적 텍스트 한 비트를 배치해야 할 때 이것을 사용했습니다.세 부분을 연결하여 업데이트하는 것보다 큰 텍스트 덩어리를 한 부분으로 업데이트하는 것이 더 쉬울 것입니다.

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

다음은 포인터를 사용하여 메모리를 복사하는 FillStringChecked를 궁극적으로 호출하는 일부 String.Concat 메서드에 대한 실제 코드입니다(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!";

즐기다!

아, 가장 빠른 방법은 다음과 같습니다.

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

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

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

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

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

어떤 경우에는 정확한 목적을 위한 디자인이므로 String.Format이 가장 빠르다고 제안합니다.

실제로는 사용 패턴에 따라 다릅니다.
사이의 상세한 벤치마크 string.Join, string,Concat 그리고 string.Format 여기에서 찾을 수 있습니다: String.Format은 집중적 로깅에 적합하지 않습니다.

String.Format은 연결용으로 설계되지 않았으므로 날짜와 같은 다양한 입력의 출력 형식을 지정하기 위한 설계이므로 권장하지 않습니다.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top