본문 바로가기

C#

C# StringBuilder로 문자열 성능 최적화

문자열은 불변입니다. 따라서 반복적인 "+" 연결은 매번 새로운 문자열을 생성하여 메모리 할당과 복사를 유발합니다. StringBuilder는 내부 버퍼를 사용해 이러한 불필요한 할당을 줄여 성능을 크게 향상합니다.

1. 언제 StringBuilder를 써야 하나

다음과 같은 경우 StringBuilder 사용을 권장합니다.

- 루프에서 많은 횟수로 문자열을 이어 붙일 때입니다.
- 결과 문자열 길이를 예측하거나 커질 가능성이 클 때입니다.
- 동적으로 조립되는 로그, CSV, 대량 텍스트 생성 시입니다.

다음과 같은 경우는 굳이 StringBuilder가 필요 없습니다.

- 연결 횟수가 매우 적고 짧은 경우입니다.
- 컴파일 타임 상수 결합(예: "A" + "B")은 컴파일러가 한 번의 상수로 접습니다.
- string.Concat으로 소수의 조각만 결합하는 단발성 작업입니다.

2. 기본 사용법과 필수 팁

기본은 Append/AppendLine으로 누적하고 마지막에 ToString을 한 번만 호출합니다. 예상 크기를 안다면 capacity를 미리 지정하면 재확장을 줄일 수 있습니다.

using System.Text;

var sb = new StringBuilder(capacity: 1024);
for (int i = 0; i < 5; i++)
{
    sb.Append("Item ");
    sb.Append(i).AppendLine();
}

// 서식이 필요하면 AppendFormat 사용
int sum = 1234567;
sb.AppendFormat("합계: {0:N0}원", sum);

string text = sb.ToString(); // 마지막에 한 번만 호출

재사용이 필요하다면 Clear로 내용만 지우고 버퍼는 유지하여 추가 할당을 줄일 수 있습니다.

sb.Clear();
sb.EnsureCapacity(2048); // 필요 시 버퍼를 넉넉히 확보

주의사항입니다.

- StringBuilder는 스레드 안전하지 않습니다. 스레드 간 공유하지 않습니다.
- Append 내에서 보간 문자열($"...")을 과도하게 쓰면 중간 문자열이 생길 수 있습니다. 루프에서는 Append/AppendFormat 조합이 유리합니다.
- ToString을 여러 번 호출하면 그때마다 새 문자열이 생성됩니다. 한 번만 호출합니다.

3. 성능 비교 예제

아래 코드는 많은 숫자를 이어붙일 때 "+"와 StringBuilder의 수행 시간을 비교합니다. 환경에 따라 수치는 달라지지만 패턴은 일관됩니다.

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

class Program
{
    static void Main()
    {
        const int N = 10_000;

        // JIT 워밍업
        _ = string.Concat("a", "b", "c");
        _ = new StringBuilder().Append("a").Append("b").Append("c").ToString();

        GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();

        var sw = Stopwatch.StartNew();
        string s = string.Empty;
        for (int i = 0; i < N; i++)
        {
            s += i; // 매 반복 시 새 문자열 생성
        }
        sw.Stop();
        Console.WriteLine($"+ 연산: {sw.ElapsedMilliseconds} ms, 길이={s.Length}");

        GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();

        sw.Restart();
        var sb = new StringBuilder(capacity: N * 4);
        for (int i = 0; i < N; i++)
        {
            sb.Append(i);
        }
        string result = sb.ToString();
        sw.Stop();
        Console.WriteLine($"StringBuilder: {sw.ElapsedMilliseconds} ms, 길이={result.Length}");
    }
}

일반적으로 반복 연결에서는 StringBuilder가 시간과 메모리 양면에서 유리합니다.

4. 자주 쓰는 API 요약

- Append/AppendLine: 값 누적, 줄바꿈 포함 여부 선택입니다.
- AppendFormat: 서식 문자열 지원입니다.
- Insert/Replace/Remove: 중간 삽입, 교체, 제거입니다.
- EnsureCapacity: 예상 길이만큼 버퍼를 확보합니다.
- Clear: 내용을 비우고 버퍼는 유지합니다.
- ToString: 최종 문자열 한 번만 생성합니다.

5. 마무리

문자열 결합이 많은 코드에서 StringBuilder를 도입하면 즉각적인 성능 개선을 기대할 수 있습니다. 특히 루프 안에서는 capacity를 미리 잡고 Append 계열 메서드를 사용하며, 마지막에 한 번만 ToString을 호출하는 패턴을 습관화하면 좋습니다.

'C#' 카테고리의 다른 글

C# 컬렉션 초기화와 Index/Range  (1) 2026.04.13
C# 애트리뷰트 (Attribute) 정의와 활용  (0) 2026.04.10
C# Enum 활용법  (0) 2026.04.10
C# var와 타입 추론  (0) 2026.04.09
C# 람다 식 (Lambda Expression)  (1) 2026.04.09