login register Sysop! about ME  
qrcode
    최초 작성일 :    2005년 05월 16일
  최종 수정일 :    2006년 09월 25일
  작성자 :    Loner (유경상)
  편집자 :    Loner(유 경상)
  읽음수 :    27,575

강좌 목록으로 돌아가기

필자의 잡담~

유수석은 현재 드원 소프트웨어의 수석 컨설턴트로 근무하고 계시며, .NET 아키텍처 및 컨설팅과 관련한 일을 하고 있습니다. 고수들 사이에서는 이미 알려져 있는 유명인이며, 제가 아는 한 김현승(닷넷엑스퍼트)과 더불어 국내 .NET 기술분야의 최고 고수로 평가받고 있는 둘째가라면 서러워 할 인물입니다.
(특이하게도 본인은 서러워하지 않음. 이상하게 주변 사람들이 더 서러워함)

장기간의 회유와 커피접대를 거쳐..

드디어, 절대로!! 다른 사이트나 블로그에 퍼감질을 허용하지 않는 그분의 글을 태오 사이트에서도 함께 하기로 하였습니다. 쉽게 알수 없는 고급스러운 비법 강좌들이 이어진다고 기대해도 좋습니다.

이 강좌는 외부 펌 금지 글입니다
페이지 링크는 가능하나, 내용 자체를 퍼가서 자신의 블로그에 올리는 것은 금지하고 있습니다.
양해해 주시기 바랍니다.

참고로, 유수석님의 블로그는 http://www.simpleisbest.net 입니다
현재 강좌의 원본 글의 링크는 http://www.simpleisbest.net/archive/2005/05/16/147.aspx 입니다.


첫 기술 이야기로 문자열 이야기를 좀 해볼까 합니다. 문자열은 가장 기본적이면서 가장 중요한 데이터 타입 중 하나입니다. 하지만 대개 개발자들이 그다지 중요하게 생각하지 않는 데이터 타입들 중 하나이기도 하지요. 대수롭지 않게 작성하는 코드가 전체적인 프로그램 성능에 영향을 미칠 수도 있다는 점을 알리기 위해 키보드를 잡았습니다.

편의상 기술 블로그는 존칭을 생략하겠습니다. 그리고... 제가 예전부터 해보고 싶었던 '딴지일보' 스타일의 글을 써보고자 합니다. 대개 기술문서는 딱딱하기 그지 없고 재미 없는 글입니다. 게다가 잘 이해가 안가는 내용이라면 지하철에서 서서 읽을 때도 졸음을 유발하곤 하지요. (적어도 제 개인 경험상은...) 그래서 평소에 해보고 싶었던 것은 딴지일보 스타일로 글을 쓰는 것이였습니다. 이런 글이라면 기술 문서라도 킥킥거리면서 재미있게 읽을 수 있지 않을까 생각했던 거죠.

그래서 질러보기로 했습니다. 아직은 글 쓰는 실력이 서툴르고 처음 시도인 만큼, 완전한 딴지 스타일의 글을 쓰진 않겠습니다. 첨부터 '독자제위', 'X까' 등등의 용어를 구사하다가 욕을 먹을 수 있으므로 처음에는 약간의 딴지 스타일로만 글을 쓰고, 좀더 딴지 스타일의 글을 연구하고, 여러분들의 피드백을 받아 본 후에 추후 글 스타일을 바꾸도록 하겠습니다. 많은 피드백 부탁 드립니다.

Story about StringBuilder

요번 글의 핵심은 StringBuilder내부를 알자이다. StringBuilder의 내부를 함 까보자는 얘기이다. 닷넷 프레임워크의 내부를 까보는데 도움이 되는 것은 뭐니 뭐니 해도 소스 코드이다. 닷넷 프레임워크의 StringBuilder 클래스의 내부를  Reflector를 통해 살짝 엿보면 므흣한 내용을 많이 알 수 있다. 문자열 이야기의 첫번째인 StringBuilder 에 대한 썰을 풀어 보자...

Immutable String

결혼 생활에서 항상 주의할 것이 있다. 연애질 할 때는 가끔 만나기 땀시 (뭐 주구장창 붙어 사는 커플도 있다. -_-) 이것 저것 신경도 쓰지만, 일단 결혼에 골인하고 나면 눈 벌어지면 보는 사람이기 때문에 서로에 대해 소홀해 질 수 있다는 거다 (무난한 결혼 생활을 유지하기 위해 특히 남성 분들은 신경을 써야 할 것이다). 닷넷 프로그래밍에도 매우, 극히, 엄청나게 자주 대하는 데이터 타입이 문자열(string)일 것이다. 매우 흔하기 때문에 코드 작성시 문자열 타입을 크게 고려하지 않는 경우가 많다. 하지만 문자열 타입도 세심한 주의가 필요한 데이터 타입이다. 어떤 주의가 필요한가를 알아보기 위해 '문자열 이야기' 씨리즈를 만들었다. 모두 몇 차례의 문자열 이야기가 나갈지는 나도 모른다. 하지만 생각나는 대로 글을 올릴 터이니 시간 나는 대로 탐독하시길...

독자들이 처음 닷넷을 배운 때를 눈을 감고 생각해 봐라. 읽었던 책에서 닷넷의 문자열은 모 Jxxx 언어와 같이 문자열이 immutable 이란 이야기가 생각날 것이다. 왜 닷넷에서 문자열이 immutable 인가에 대해서는 여기서 시시콜콜 논하지 않겠다. 뭐 좋으니까 immutable 이겠지 하고 걍 넘어가자. (나중에 시간나면 문자열이 왜 immutable이 되었나 함 다루어볼까 한다. 믿거나 말거나... -_-)

문자열이 immutable 이란 얘기는 할당된 문자열을 바꿀 수 없다는 얘기 인데... 문자열을 바꾼다는 얘기는 기존 문자열에 추가 문자열을 붙이거나 기존 문자열의 문자(들)를 바꾸는 등의 문자열 연산을 존재하는 문자열에 대해서 수행할 수 없다는 얘기가 되겠다. 그렇다면 어떻게 문자열 연산을 수행할까? 간단하다. 문자열 연산의 결과로서 기존 문자열이 바뀌는 것이 아니라 새로 문자열을 만들면 된다. 예를 들어 "aaa" 라는 문자열에 "bbb"를 연결하는 문자열 연산을 생각해 보자. 요런 문자열 연산을 유식하게 문자열 연결(concatenate) 이라고 한다. C/C++ 스타일이라면 문자열 버퍼를 대략 큼지막하게 작성해 놓고, 이 문자열 버퍼에 "aaa"를 할당하고 그 문자열 버퍼 뒤에 "bbb"를 쑤셔 넣으면 된다. 하지만 닷넷은 "aaa" 문자열 뒤에 문자열 "bbb"를 연결하면 새로운 문자열 "aaabbb" 가생성 된다. C/C++에서는 2개의 문자열이 있으면 되는 반면("aaa"와 "aaabbb"를 위한 덩치 큰 문자열과 "bbb" 문자열), 닷넷은 세 개의 문자열이 소요된다. 대략 낭비가 아닐 수 없다.

StringBuilder Introduced

이런 전차로... 문자열 연결과 같은 연산을 하라고 만들어 놓은 것이 있으니 그것이 System.Text.StringBuilder 클래스 이다. 대다수의 닷넷 서적과 온라인 문서들을 살펴보면 문자열 연결에 StringBuilder를 사용하는 것이 좋다고 되어 있다. 대략적인 논지는 닷넷의 문자열은 변경 불가능이고 이 때문에 문자열 연산을 수행하면 새로운 문자열 객체들이 계속 생성되어 비효율 적이니 문자열 연결 마다 문자열이 생성되지 않는(?) StringBuilder를 쓰라는 것이다. 게다가 어떤 글은 StringBuilder의 성능까지 운운하기도 한다.

그렇다면 StringBuilder는 대체 어떻게 문자열 연결을 수행할까? StringBuilder라고 용빼는 재주는 없다. StringBuilder 역시 내부에 문자열 버퍼를 가지고 있다. 그리고 문자열 연결이 수행될 때마다 즉, Append 혹은 AppendFormat 메쏘드가 호출될 때마다 이 내부 퍼버에 연결하고자 하는 문자열을 복사하는 것이다. 그런데 StringBuilder의 내부 문자열 버퍼가 바로 System.String 타입이다.

이런... 문자열은 immutable 이라 하지 않았나?  적어도 CLR 내부에서는 문자열이 immutable 이란 말은 완전히 구라가 되겠다. 말이 immutable 이지 CLR 내부와 mscorlib.dll 내부에서는 FillString 이란 static 메쏘드(FillStringEx 등등 문자열을 바꾸는 메쏘드 세트가 있다)를 호출하여 새로운 문자열을 할당하지 않고 기존 문자열을 바꿀 수 있다 (이런 치사한... -_-). 우리 같은 미천한 프로그래머는 FillString 메쏘드를 호출할 수 없다. 이 녀석이 private 메쏘드이기 때문이다. 대략 뷁 스럽다 아니할 수 없다.

* 불가능은 없다... 대략 귀찮을 뿐...
FillString 메쏘드 호출이 전혀 불가능한 건 아니다. Reflection을 이용하면 호출할 수 있다. 하지만 이렇게 까지 할 가치는 없다고 본다. 기존 문자열 변경을 위해 Reflection 까지 쓴다는 건 배보다 배꼽이 클 수 있기 때문이다. 구체적인 내용은 다른 문자열 이야기에서 썰을 풀겠다. 여기서 이야기 하면 글이 대략 난감하게 길어진다는...

다시 본론으로 돌아와서, StringBuilder는 내부 문자열 버퍼를 만들어 놓고 Append 류의 메쏘드가 호출될 때마다 내부 문자열 버퍼를 채워 가는 것이다. 예상 했던 바다. 하지만 문자열 버퍼가 다 차버린다면? 마이크로소프트 CLR 개발팀이 짱구가 아닌 다음에야 이러한 경우를 대비하지 않았을리 없다. StringBuilder는 내부 버퍼가 부족할 때마다 새로운 내부 버퍼를 생성하며 그 크기는 기존 버퍼의 2배가 되도록 한다. 이때 중요한 것은 앞서 말한 것처럼 내부 버퍼가 System.String 이라는 점이며 이는 곧 새로운 문자열을 만드는 것이 되겠다.

눈치빠른 독자라면 조금씩 냄새를 맡았을 것이다. 글타. 다시 강조하지만 StringBuilder라고 용빼는 재주가 있는 것이 아니다. StringBuilder를 사용하더라도 새로운 문자열의 할당은 발생할 수 있는 것이며 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을 말 그대로 '만들어'낼 때는 반복적으로 문자열은 연결해야 하는 경우가 마니 생긴다. 안 해봤다고? 그럼 지금이라도 메뉴 웹 컨트롤을 만들어 보라...


authored by


 
 
.NET과 Java 동영상 기반의 교육사이트

로딩 중입니다...

서버 프레임워크 지원 : NeoDEEX
based on ASP.NET 3.5
Creative Commons License
{5}
{2} 읽음   :{3} ({4})