SimpleIsBest.NET

유경상의 닷넷 블로그
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.

정말 간만에 글을 쓰는 군요... 2주 정도 된 듯... 요즘엔 귀차니즘이 아니라 정말 "시간이 없어서" 글을 못 올리고 있답니다. 뭐 설 연휴에 글을 쓸 수도 있었겠지만... 민족 최대의 명절이라는 설에 기술 관련 글이나 쓰자니 그것도 안습이고 해서... 음냐...

어찌되었건 오랜만에 글을 올리네요...

Performance Consideration of Some Collections

이번 글은 우리가 많이 사용하는 몇몇 컬렉션들의 성능에 대한 이야기이다. 이 글은 SK C&C의 류 모 과장님께서 약 7-8개월 전에 알려주신 내용을 최근에 필자가 확인 사살한 경험을 싣는 것이다(그러고 보면 필자도 조낸 게으른가 보다). 이 자리를 빌어 좋은 정보를 알려주신 류 모 과장님께 감사를 드리는 바이다.

Introduction

류 모 과장님이 필자에게 알려준 내용은 NameValueCollection 클래스가 Hashtable에 비해 상당히 느리다는 내용을 담은 블로그였다.

이 블로그는 Microsoft의 닷넷 BCL(Base Class Library) 팀 블로그에서 나온 글이기 때문에 상당히 신뢰도가 높았음에도 불구하고 필자는 쉽사리 긍정하기 어려웠었는데, 그 이유는 NameValueCollection이 문자열을 키(key)로 사용하고 그 값 역시 문자열을 사용하기 때문에 boxing/unboxing이 발생하지 않으며 Hashtable에 비해 NameValueCollection이 상대적으로 구현 코드가 짧았기 때문이었다(reflector로 대충 훓어 봤을 때). 그래서 필자는 키와 값이 모두 문자열인 간단한 컬렉션을 사용할 때는 상대적으로 무겁다고 생각했던 Hashtable 보다는 NameValueCollection을 선호했었으며, 닷넷 프레임워크 2.0이 등장한 후에는 NameValueCollection 혹은 Dictionary<> 클래스를 많이 사용해왔던 것이다.

그런데... BCL 팀에서 NameValueCollection이 Hashtable 보다 느리다고 하니 필자가 쉽게 수긍이 갈리 만무하다. 아놔... 필자도 사회적 지위와 체면 그리고 자존심이 있지, BCL 팀이 Hashtable이 빠르다고 하면 그것을 쏠랑 따라갈 수 없지 않은가?

Do test!

NameValueCollection이 느리다는 말을 들은 필자는 살짝 울컥 했지만, BCL 팀의 블로그에서 나온 내용이니 안 믿을 수도 없던 차에 블로그의 글이 컬렉션에 10만개의 키/값 쌍을 집어 넣고 40만 번의 조회(lookup)을 수행하는 것에 주목했다. 여전히 NameValueCollection이 가볍다고 생각하던 필자는 우매한 똥 고집을 부리기 시작했다.

"컬렉션에 100개 미만의 키/값 쌍이 들어 있다면 NameValueCollection이 더 빠를 거야..."

그래서... 직접 테스트를 수행하는 코드를 짜 보았다. 테스트는 10, 100, 300, 500, 1000, 2000, 5000, 10000, 50000 개의 문자열 키/값 쌍을 각각 집어 넣고 이들의 4배에 해당하는 조회를 수행하는 것이었다. 필자의 예상으론 작은 개수의 키/값 쌍에서는 NameValueCollection이 우수할 것이고 많은 수의 키/값 쌍에 대해서는 Hashtable 이 우수한 성능을 내리라고 생각했다. 추가적으로 Hashtable과 Dictionary<> 의 성능 비교도 동일한 방법으로 수행했는데, 필자의 예상으로는 boxing/unboxing 이 발생하지 않는 Dictionary<> 클래스의 성능이 우수하리라 예측했다.

테스트에 사용한 코드는 매우 간단하다. 닷넷 프레임워크 2.0을 사용하였다는 것과 for 문을 사용하여 반복적으로 테스트할 컬렉션에서 주어진 키에 해당하는 값을 찾는 방식이라는 정도만 밝히는 바이다.

Test Result

테스트 결과는 매우 참담했다. 테스트 결과는 필자의 생각이 정말 쓸데없는 똥 고집에 쑛도 모르는 인간의 자존심이었다는 것을 증명해주었다. 컬렉션의 아이템이 많던 적던 관계 없이 모든 상황에서 Hashtable의 성능은 NameValueCollection의 성능을 능가하고 있었다. 그것도 BCL 팀의 글에서 지적한 것 보다 더 많은 차이로 Hashtable의 성능이 좋았던 것이다. 테스트 결과에 대한 구체적인 수치는 중요하지 않다. 성능테스트 수치란 것이 테스트를 수행한 컴퓨터마다 다를 수 있기 때문이며, 몇 배 빠르다는 것도 컴퓨터 마다 다르게 나타날 수 있기 때문이다. 중요한 것은 Hashtable이 NameValueCollection 보다 몇 배 빠르다는 것이며 이는 컬렉션에 추가한 아이템의 개수와 상관 없다는 점이 되겠다.

테스트 결과에서 한가지 위안으로 삼을 수 있었던 것은 필자의 예상대로 Dictionary<> 클래스가 Hashtable 보다 성능이 약간 더 좋았다는 점이었다. (Dictionary<> 클래스는 Generics 덕택에 형 변환에 필요한 코드를 사용하지 않아도 되기 때문으로 생각된다)

Hashtable, NameValueCollection, Diectionary<> 세 컬렉션은 내부적으로 모두 해시 테이블 알고리즘을 사용하는데... 왜 이리 성능 차이가 심하게 나는 걸까?

Why ?

결과에 놀란 필자... 똥 줄 타게 원문을 다시 천천히 읽어 보았다. 처음에는 원문의 테스트 결과만을 대충 읽어 보았고, 그 결과를 믿지 못해서 자체 테스트를 했기 때문이었다. 원문에는 왜 NameValueCollection이 느릴 수 밖에 없는가를 상세히 그리고 친절하게 설명해 주고 있었다. 처음부터 글을 자세히 읽어 보았다면 직접 테스트하는 삽질을 하지 않을 수도 있었을 것이다.

NameValueCollection 이 느린 이유는 다른 키/값 기능을 제공하는 컬렉션과 다르게 이 클래스만이 제공하는 기능들을 유지하기 위해서 이다. NameValueCollection 클래스는 Hashtable 이나 Dictionary<> 클래스와는 달리 인덱스를 통해서 값을 액세스하는 기능을 제공한다. 즉, 다음과 같이 값들을 액세스할 수 있다.

NameValueCollection collection = new NameValueCollection();

collection.Add("key1", "value1");

collection.Add("key2", "value2");

 

Console.WriteLine(collection["key1"]);

Console.WriteLine(collection["key2"]);

Console.WriteLine(collection[0]);

Console.WriteLine(collection[1]);

Hashtable을 사용했을 때, 어떤 값을 알기 위해서는 반드시 키가 필요하다. 하지만 NameValueCollection은 키 뿐만 아니라 마치 배열처럼 인덱스를 사용하여 컬렉션에 추가한 순서대로 값들을 액세스할 수 있는 것이다. Hashtable 이나 Dictionary<>는 인덱스를 제공하지 않을 뿐만 아니라 추가한 순서대로 값을 읽는다는 보장 역시 없다.

NameValueCollection은 이러한 기능을 유지하기 위해 내부적으로 추가적인 작업을 수행한다. Reflector로 필자가 확인해 본 결과(이번에는 대충이 아니라 졸라 꼼꼼히 살펴봤다)는 더욱 놀라웠는데... 주어진 키로 값을 찾기 위해서 NameValueCollection은 내부에 private 멤버로서 Hashtable을 사용하고 있었으며, 배열처럼 값들을 액세스하기 위해 값을 별도의 ArrayList에 기록해 두고 있었다. 즉, 인덱스가 사용되면 ArrayList로부터 주어진 인덱스의 '값'을 읽어 반환한다는 것이다.

NameValueCollection이 느린 결정적인 역할을 하는 또 하나의 이유로는 NameValueCollection 클래스가 하나의 키에 대해 여러 개의 값을 추가할 수는 능력 때문이다. 다음 코드를 살펴보자.

NameValueCollection collection = new NameValueCollection();

collection.Add("key1", "value1");

collection.Add("key1", "value1-1");

collection.Add("key1", "value1-2");

collection.Add("key2", "value2");

 

string[] values = collection.GetValues("key1");

 

Console.WriteLine(collection["key1"]);   // 결과값 : value1,value1-1,value1-2

NameValueCollection이 하나의 키에 대해 여러 값을 기록할 수 있기 때문에 GetValues() 메쏘드를 제공하여 문자열의 배열(string [])을 반환할 수 있도록 되어 있으며, 인덱서(indexer)를 사용하여 문자열 값을 반환하는 경우에는 여러 값들이 콤마로 구분되어 단일 문자열로 반환된다. 사실 이러한 기능은 필자도 잘 써먹지 않는 기능인데, HttpRequest 클래스의 QueryString 속성이 왜 NameValueCollection 으로 구현되어 있는가를 알 수 있게 해주는 기능이다. URL의 QueryString은 하나의 이름에 여러 값이 사용될 수 있기 때문이다.

어찌 되었건 이러한 기능을 제공하기 위해 NameValueCollection은 키/값 쌍을 구성할 때 "값"으로써 ArrayList 객체를 값으로 사용한다. 그 이유는 하나의 키에 여러 문자열 값을 기록해 둘 수 있도록 "값"에 ArrayList를 사용하는 것이다. 만약 100개의 키/값 아이템이 NameValueCollection 내에 존재한다면 100개의 ArrayList 객체가 내부 Hashtable에 기록되어 있고 ArrayList가 다시 실제 '값'으로 사용되는 문자열(들)을 기록하고 있는 것이다. collection["key1"] 이란 코드가 사용되면 주어진 키로 내부 Hashtable을 뒤져(이건 빠르다) ArrayList 객체를 알아낸다. 그리고 ArrayList에 포함된 값들을 하나의 문자열로 합친 후에 그 결과를 반환하는 것이다. 이 정도 되면 NameValueCollection이 Hashtable에 비해 늦을 수 밖에 없다.

마지막으로 Why are NameValueCollection lookups slower than Hashtable? 글에서 지적한 "컬렉션에 아이템을 추가하고 제거하는 성능" 비교에서도 NameValueCollection이 느릴 수 밖에 없는 이유를 알 수 있다. 내부적으로 Hashtable을 사용하고 있음에도 불구하고 인덱스를 통한 액세스를 위해 ArrayList를 유지해야 하고, 새로운 키/값 아이템이 추가될 때마다 새로이 ArrayList 객체를 할당해야 하기 때문에 추가 속도는 느릴 수 밖에 없고, 제거 속도는 더욱 더 느릴 것이다(순차적인 액세스를 위해 추가적인 작업이 필요).

이제 Dictionary<> 컬렉션 이야기를 조금만 해 보자. Dictionary<> 클래스는 내부적으로 해시 알고리즘을 자체적으로 사용하고 있다. 즉 Dictionary<> 클래스는 NameValueCollection과 달리 내부적으로 Hashtable을 사용하지 않는다는 얘기이다. 그리고 Dictionary<> 클래스는 Generics를 기반으로 하므로 호출자가 형 변환을 수행하지 않아도 되기 때문에(사실 컴파일 타임에 형 검사가 가능하다), 런타임에 형 변환 검사를 수행해야 하는 Hashtable에 비해 약간 더 빠를 수 밖에 없다.

Conclusion

결론은 Hashtable이 더 빠르니 Hashtable을 쓰라는 것이 아니다. 만약 그렇다면 달랑 블로그 글만 참조하고 말았을 것이다. 필자가 하고 싶은 말은 컬렉션을 사용할 때 무조건 성능을 고려하는 것 보다는 어떤 용도로 컬렉션을 사용할 것인가를 고려해서 적절한 컬렉션을 선택해야 한다는 것이다. 원하는 기능을 2개 이상의 컬렉션이 지원한다면 그때 가서 성능이나 개발 편의 등등을 고려해도 늦지 않다.

NameValueCollection이 비록 성능적으로 느리다고 하지만 다른 키/값 쌍을 제공하는 컬렉션이 지원하지 않는 기능을 지원하기 때문에 NameValueCollection을 무조건 기피할 것은 아니라고 본다. 하나의 키/값이 모두 문자열 타입이고 키에 여러 값을 기록해야 한다면, 굳이 Hashtable 기반의 새로운 컬렉션을 만드느니 NameValueCollection 을 사용하는 것이 유리할 것임은 너무도 당연하다.

Dictionary<> 클래스는 키/값의 타입을 명시할 수 있기 때문에 성능적으로는 가장 우수하다. 하지만 키의 타입이 한 종류가 아니고 int, string 등 복수개의 타입을 써야 하거나 값의 타입도 한 종류의 타입이 아닐 때는 사용이 어려운 컬렉션이다. Dictionary<object, object> 로 선언하여 사용할 수도 있겠지만 그러느니 차라리 Hashtable을 사용하는 것이 나을 것이다. Dictionary<> 클래스는 키와 값의 타입이 각각 한 가지로 명확할 때 사용하면 최상의 성능을 얻을 수 있는 컬렉션이 될 것이다.

Hashtable은 충분한 성능을 제공하면서도 다양한 타입의 키와 다양한 타입의 값을 모두 지원하는 키/값 쌍 기반의 컬렉션이다. ASP.NET 의 Cache 객체를 비롯하여 성능적으로 민감한 많은 객체들이 내부적으로 Hashtable을 사용하고 있을 정도로 닷넷 세상에서 널리 사용하는 컬렉션이므로 Dictionary<> 와 같은 컬렉션으로 처리하기 힘든 다양한 타입의 키와 다양한 타입의 값을 갖는다면 Hashtable 을 사용하는 것이 좋을 것이다.

마지막으로... 필자처럼 이러 이러할 것이다라는 추측으로 똥 고집을 부리지 말기 바란다. 피곤질 수가 있다... -_-;



Comments (read-only)
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 땡초 / 2007-02-23 오후 6:28:00
재미있네요~

간과하기 쉬운 부분이고, 생각지도 못햇던 부분인데,,

재미있게 잘 보고 갑니다.
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 망고 / 2007-02-24 오전 12:58:00
잘 읽었습니다~
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 허접이 / 2007-02-25 오후 10:30:00
잘 읽었습니다.
사실 제일 많이 사용하는 클래스 인데요. 속도나 기능을 감안한다면 매우 중요한 내용입니다.
감사합니다.
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 도우즈 / 2007-02-26 오전 12:30:00
쥔장님 글은 하나하나 놓칠것이 없습니다.
막연하게 알고 있는 부분을 쥔장님은 확실하게 매듭을
지어주시네요..
#흐흠... 이건 좀 충격적인... / 블로그쥔장 / 2007-02-27 오후 4:17:00
이 글에서 BCL 팀의 블로그를 링크로 걸어 놓았습니다만...
그 링크가 잘못되어 있었습니다. 저도 방금 알게 되어 수정했습니다만...
아무도 지적을 안했다는 건 좀... 몇 가지를 생각해 볼 수 있겠습니다.

* 아무도 링크를 클릭해 보지 않았다.
* 링크를 클릭해 보았지만 링크가 깨져서 직접 URL을 입력했다. 하지만 귀차니즘으로 쥔장에게 알려주지 않았다. (쥔장 엿먹어봐라? ^^)

원문을 읽어 보는 것은 대단히 중요합니다.
제 글에서 다루지 않은 내용도 포함되어 있으니까요... 가급적 읽어 보시길...
음냐뤼... T_T
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 원~ / 2007-02-27 오후 4:19:00
ㅋㅋ..직접 테스트까지..^^
훌륭하네요..전 블로그 내용만 보고 믿었는데..역시 프로그램의 고수가 되는 길은 멀고도 험하네요...^^;;
분당오시면 연락주세요~^^
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 블로그쥔장 / 2007-02-27 오후 11:15:00
이 글 쓰게된 동기가 다 류과장님 덕이지요.
전 그냥 제 쓸데없는 자존심 세워볼려고 테스트 해본 거구요...
덕분에 Dictionary<> 클래스가 상당히 효율적이라는 것을 확인 사살했을 뿐입니다.
감솨합니당....
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / lancers / 2007-02-28 오후 5:14:00
어라, 이런거 하는거 보면 시간이 많이 남아 도나 보다. ㅋㅋㅋ
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 블로그쥔장 / 2007-03-01 오후 7:16:00
랜서쓰 아찌...
시간이 남지는 않지만... 그래두 재밌자나?
우리 하는 일이 항상 그렇구 그런데... 재미 마저 없으면 GG 쳐야 하는거 아녀?
^^
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 좋은 내용이네요. / 2007-03-04 오전 10:51:00
저도 hashtable을 소트를 할 일이 생겨서 고민하다가 sortedlist 를 사용했는데 나중에 sortedlist 에 대한 글도 볼 수 있으면 좋겠습니다.
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / arang100 / 2007-03-06 오전 9:09:00
전에 왜 비슷한 Collection을 2개나 만들어 놓았을까 생각했었는데, 이런 이유가 있었네요.
저도 문자열을 쓸때는 당연히 NameValueCollection이 성능이 좋고 문자열이 아닐때는 Hashtable이 성능이 좋다고 막연히 생각했는데...
재미있네요^^ 닷넷에 대한 흥미가 팍팍 돋아나는 그런 강좌였습니다.
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 탱옹 / 2007-03-08 오후 5:09:00
어라, 이런거 하는거 보면 시간이 많이 남아 도나 보다. ㅋㅋㅋㅋㅋ (난 ㅋ가 5개)
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 노가리. / 2007-03-12 오전 12:55:00
좋은 강좌 감사해요..^^
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 눈꽃천사 / 2007-03-13 오후 2:05:00
Why are NameValueCollection lookups slower than Hashtable? [Kim Hamilton]
첫번째 링크는 고쳐졌지만, 두번째 링크 걸린 부분은 깨져있네요.
원문도 좋지만 그 아래 Comments 가 더 인상적이네요.
감사합니다.
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 감사~~ / 2007-05-03 오전 11:14:00
좋은 강좌 내용 보관 해도 될까요?
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 정성균 / 2007-06-18 오후 5:45:00
얼마전에 읽었다가 .... 또 읽었습니다. 헤헤...
이눔의 기억력 하고는.... ㅜ.ㅡ
1.x 때 부터... 주로 Hashtable 사용해왔는데... 그나마 그걸루 위로됩니다.ㅋㅋ...^^
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 크릉크릉 / 2008-09-26 오전 11:06:00
잘읽었어요 항상 감사합니다.
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / hym77 / 2008-12-13 오전 12:51:00
잘 읽었습니다.

hashTable에 대해서 다시 한번 생각을 하게 되네요~
#re: Hashtable, Dictionary<>, NameValueCollection의 성능에 대하여... / 스파우터 / 2009-09-09 오후 1:06:00
좋은 내용 감사합니다. 내용 담아가도 되죠??^^