SimpleIsBest.NET

유경상의 닷넷 블로그
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.
문자열 이야기 두 번째 입니다. 이번 글도 마찬가지로 딴지 스타일로 적어봤습니다. 머 완전한 딴지 스타일은 아니고... 세미 딴지 정도? 약간 얌전하게... 역시 기술 자료를 딱딱하지 않게 쓴다는 것이 힘들군요. 쫌더 딴지 스타일에 가깝게 쓰면 몰라도 세미 딴지 스타일은 좀 힘들다는...

Story about StringBuilder - (II)

앞서 문자열 이야기 포스트에서 StringBuilder에 대해 간략한 설명을 해보았다. 이번엔 좀더 꽁꼬 깊숙히 StringBuilder의 내부를 살펴보도록 하자.

Inside StringBuilder

StringBuilder가 생성되면 디폴트로 16문자를 담을 내부 문자열 버퍼를 생성한다. 요것이 오늘의 중요한 뽀인또가 되것다. 연속되는 Append 호출이 발생하여 문자열이 16문자를 초과하게 되면 2배인 32 문자를, 그 후에 또 버퍼가 초과되면 64, 128, 256, 512, ... 이런식으로 계속 새로운 버퍼를 할당한다. 새로운 버퍼를 할당하는 것 외에도 좆지(험험) 못한 것은 기존 버퍼의 내용을 새 버퍼로 복사해야 한다는 것이다. 그리고 나서 기존 문자열 버퍼는 어떻게 하냐고? 그걸 나한테 물으면 어떡하나? GC(Gargabe Collection)에게 물어봐야지.

오늘의 또 한가지 뽀인또는 StringBuilder.ToString() 메쏘드다. StringBuilder를 통해 기껏 문자열을 만들어 대면 뭐하나? 정작 필요한 것은 문자열, 즉 System.String 타입인걸... 그래서 항상 우린 StringBuilder.ToString()을 호출하여 문자열을 받아 낸다. StringBuilder.ToString 메쏘드는 새로운 문자열을 할당하고 내부 문자열 버퍼의 내용을 이 새로운 문자열에 복사하여 반환한다. StringBuilder.ToString이 새로운 문자열을 할당하는 것을 주목할 필요가 있겠다. 만약 새로운 문자열을 만들어 반환하지 않고 StringBuilder의 내부 문자열을 그대로 반환한다면, 하나의 문자열에 대해 2개 이상의 참조(reference)가 존재하게 된다. 만약 StringBuilder 가 재사용되어 변경이 가해지면 문제가 발생하게 될 것이다. ToString() 이 호출된 후, StringBuilder는 버퍼를 초기화 하여 StringBuilder가 재사용될 수 있도록 만드는 것 역시 알아두면 피가되고 살이되는 지식이다. 이쯤 되면 펜을 들고 메모할 필요를 느끼지 않는가?

StringBuilder의 내부를 까보면 생각보다 간단하지 않다는 것을 알 수 있다. 크기가 자동으로 증가되어야 하므로 StringBuilder는 내부 문자열 버퍼가 넘치는 것을 막기 위해 할당된 내부 문자열 버퍼의 크기를 유지하며 또한 이 내부 버퍼에 기록된 문자의 개수 역시 유지해야 하는 등의 오버헤드를 갖고 있다. StringBuilder는 빠르지 않다. StringBuilder에 대한 다양한 성능 테스트(난중에 성능 테스트 자료를 올리겠다. 지금 성능 테스트 자료까지 올리면 블로그 하나가 너무 빡세지기 때문에...)를 살펴보아도 StringBuilder의 오버헤드가 적지 않다는 것을 알 수 있다.

StringBuilder Usage

StringBuilder를 쓰지 말라고 ? 전혀 쓰지 말라는 얘기가 아니다. StringBuilder를 쓸때와 그렇지 않을 때를 명확히 구분하는 것이 좋다는 얘기이다. 이쯤 되면 독자제위들은 어떤 때 StringBuilder를 쓰지 말아야 하는가를 말할 것이라고 예상할 것이다. 반대로 StringBuilder를 사용해야 하는 때는 필자 생각에는 그다지 많지 않다. 대부분의 경우 String.Concat 이나 C#, VB.NET의 + 연산자를 쓰는 것이 더 효율적이며, 반복적으로 다수(수십 ~ 수백회) 문자열을 연결하는 경우에나 StringBuilder를 사용하는 것이 좋다. StringBuilder를 사용하지 않을 때 사용할 문자열 연산 방법은 쪼금 있다 설명하기로 하고 여기서는 StringBuilder의 올바른 용법에 대해서 살펴보자.

StringBuilder를 쓸 때는 가급적 버퍼 크기를 명시하는 것이 좋다. 즉 달랑 디폴트 생성자로 StringBuilder를 생성하지 말고 생성자에 버퍼 크기를 주라는 것이다. 버퍼 크기는 대략적으로 예상되는 크기를 적어주면 되겠다. 그렇다고 무조건 겐또로 찍는 것도 곤란하지만 대략적으로 크기를 알 수 있을 것이다. 전혀 모르겠으면 넉넉하게 크게 잡아줘라. 잘모르겠다고 ? 다음 코드를 보자.

// 좋지 못한 StringBuilder 사용법
StringBuilder sb = new StringBuilder();    // 16 문자 버퍼(문자열)를 생성
sb.Append("1234567890");
sb.Append("1234567890");    // 버퍼가 부족하므로 32 문자를 담을 새로운 버퍼를 생성하고 기존 버퍼 내용을 복사한다.
sb.Append("1234567890");
sb.Append("1234567890");    // 버퍼가 또 부족하므로 64 문자를 담을 새로운 버퍼를 생성하고 기존 버퍼 내용을 복사한다.
string s = sb.ToString();   // 새로운 문자열을 만들어서 내부 버퍼의 내용을 복사하고 반환한다.

위 코드는 4개의 문자열이 StringBuilder와 관련되어 생성되고 사용되며 이중 2개는 아주 짧은 시간 동안만 사용되고 버려진다. 반면 다음 코드는 2개의 문자열만이 사용된다.

// 그나마 나은 StringBuilder 사용법
StringBuilder sb = new StringBuilder(64);    // 64 문자 버퍼(문자열)를 생성
sb.Append("1234567890");
sb.Append("1234567890");
sb.Append("1234567890");
sb.Append("1234567890");    // 버퍼가 충분하므로 새로운 내부 버퍼를 요구하지 않는다.
string s = sb.ToString();   // 새로운 문자열을 만들어서 내부 버퍼의 내용을 복사하고 반환한다.

이 예제에서는 최종적인 문자열 크기를 알기 때문에 64라는 capacity 값을 줄 수 있었지만 최종적인 크기를 전혀 알 수 없다면 넉넉하게 capacity 값을 주는 것이 좋다. 항상 이런 말을 하면 걱정되는 것이 무식하게 딥따 큰 capacity를 주는 인간들이다. 이런 단순 무식은 명랑한 프로그래밍 문화에 아무런 도움이 못 된다. 적당히 넉넉히 주면 StringBuilder가 알아서 그 크기를 2배씩 키워 줄 것이다. 초기 값이 작으면 작을 수록 반복적으로 임시 문자열이 만들어졌다가 사라지므로 이것을 방지해 보자는 것이다.

잠시 후 알게 되겠지만 위의 두 예제 코드는 모두 삽질이 되겠다.... -_- 위와 같이 간단한 문자열 연결(concatenate)은 StringBuilder를 쓰는 것이 더 비효율적일 뿐더러, 위 예제와 같이 문자열 상수(유식하게는 문자열 리터럴 이라구도 한다)를 연결할 때는 더더욱 그렇다. StringBuilder는 커다란 문자열 버퍼에 반복적으로 수십, 수백 회의 문자열 연결하는 때가 아니라면 사용할 일이 별로 없다고 보문 되긋다. 특히 for, while, foreach 류의 반복문 안에서는 StringBuilder를 쓰는 것이 좋다. 그런 때가 언제냐고? ASP.NET에서 HTML을 말 그대로 '만들어'낼 때는 반복적으로 문자열은 연결해야 하는 경우가 마니 생긴다. 안 해봤다고? 그럼 지금이라도 메뉴 웹 컨트롤을 만들어 보라...



Comments (read-only)
#re: 문자열 이야기 (2) - StringBuilder에 대한 진실 혹은 거짓말 (II) / 아 이해 할 수가 없다. / 2007-10-09 오전 4:59:00
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder();

sb2 = sb;
sb.Append("1234567890");
Console.WriteLine("{0}", object.ReferenceEquals(sb, sb2));

sb.Append("1234567890");
Console.WriteLine("{0}", object.ReferenceEquals(sb, sb2));

sb.Append("1234567890");
Console.WriteLine("{0}", object.ReferenceEquals(sb, sb2));

위와 같이 코딩을 해봤습니다.
두번째와 이후는 16글짜가 넘었기 때문에 새로운 메모리를 생성하여 서로 다른 주소가 나올것이라고 생각했습니다.
헌데 True가 나옵니다...
실행시 오브젝트의 주소를 알수 있는방법이 있나요? 그것을 몰라서 object.ReferenceEquals를 이용하여 object의 주소를 비교해 보았습니다.
#re: 문자열 이야기 (2) - StringBuilder에 대한 진실 혹은 거짓말 (II) / 블로그쥔장 / 2007-10-09 오후 2:15:00
-_-; StringBuilder는 참조형 타입(reference type)으로써 객체 입니다.
sb2 = sb;
이 코드가 수행됨으로써 sb 와 sb2는 동일한 StringBuilder 객체를 참조하게 됩니다.
따라서 sb에 벼라별 생 SHOW를 해도 sb와 sb2는 동일한 StringBuilder 객체를 가르키고
항상 ReferenceEquals가 같다는 것이지요.
참조형 변수에서 할당 연산자(assignment operator)는 참조값(객체 주소)을 복사하는 것이지
객체 자체를 복사하는 것이 아님을 이해하셔야 합니다.
#re: 문자열 이야기 (2) - StringBuilder에 대한 진실 혹은 거짓말 (II) / 크레이그진 / 2007-11-23 오후 10:38:00
좋은글 잘 읽었습니다. 그런데 내용중 다음 부분에서 잘 이해가 되지 않습니다.

ToString() 이 호출된 후, StringBuilder는 버퍼를 초기화 하여 StringBuilder가 재사용될 수 있도록 만드는 것 역시 알아두면 피가되고 살이되는 지식이다. 이쯤 되면 펜을 들고 메모할 필요를 느끼지 않는가?

ToString()가 최초 호출된후 StringBuilder 버퍼가 재사용될수있도록 만드는것 이것이 무슨말씀이신지 이해가 안됩니다. StringBuilder의 내부버퍼를 초기화한다는 말씀이신지요.
#re: 문자열 이야기 (2) - StringBuilder에 대한 진실 혹은 거짓말 (II) / 블로그쥔장 / 2007-11-25 오후 12:30:00
ToString() 호출 이후 내부적으로 Null 문자를 추가하고 쓰레드 관련 내부 필드를 초기화하는 등의
작업을 수행합니다. 특별히 버퍼를 지워버린다거나 하는 작업은 아니므로
그렇게 크게 신경써야할 부분은 아닙니다만...
보다 중요한 것은 ToString() 호출이 새로운 문자열을 만들어 StringBuilder 내의 문자열을
복사한다는 점입니다.
#re: 문자열 이야기 (2) - StringBuilder에 대한 진실 혹은 거짓말 (II) / 크레이그진 / 2007-11-27 오후 2:18:00
아 감사합니다. 요즘 닷넷의 세계에 빠지려는 중생인데 많이 배우고갑니다.
항상 Under Hood 한 곳을 알아갈때의 재미는 정말 쏠쏠하지요. 닷넷계의 Matt Pietrek 이 되시길~ ^^
그리고 닷넷에서 리버싱도 조회가 깊으신것 같은데 다 보여주진 못하셔도 약간의 흰트글들을 올려주시면 정말 감사하겠습니다.
바라는것만 많아서 정말 죄송하구요. 좋은글들 적으시는데 다시한번 감사드립니다. ^^
#re: 문자열 이야기 (2) - StringBuilder에 대한 진실 혹은 거짓말 (II) / 조승태 / 2008-02-22 오후 5:28:00
앞글에서 저는 그냥 string만 쓴다고 했는데,,,
무식한게 덕이 되고 말았군요. ^^;

#re: 문자열 이야기 (2) - StringBuilder에 대한 진실 혹은 거짓말 (II) / 이시택 / 2008-06-11 오전 4:59:00
뉴욕에서 인턴쉽 하고 잇는 학생인데요

비베 닷넷 개발 팀이거든요 ~ 호호호 많은 더욱 되었네용 ㅎ

#re: 문자열 이야기 (2) - StringBuilder에 대한 진실 혹은 거짓말 (II) / 이시택 / 2008-06-11 오전 4:59:00
뉴욕에서 인턴쉽 하고 잇는 학생인데요

비베 닷넷 개발 팀이거든요 ~ 호호호 많은 더욱 되었네용 ㅎ

#re: 문자열 이야기 (2) - StringBuilder에 대한 진실 혹은 거짓말 (II) / 강윤태 / 2009-07-05 오후 10:35:00
안녕하세요 자바를 공부하다가 코드에서 StringBuilder 부분이 나와서 찾아보던중 들렀는데요.
ToString을 하면 StringBuilder의 버퍼에 있는 스트링을 복사해준다는 것이 맞나요?

그런다음 초기화하라는 말씀은.. 버퍼를 지우는게 아니라 NULL문자만 추가한다는게.. 초기화란 말이랑 잘 이해가 안갑니다 ㅠㅠ