[C++] STL map 과 ATL 의 map 성능 비교
| Tweet Follow @esstory |
|
지난 몇 주간 유지보수중인 프로그램의 성능 문제 때문에 Visual Studio 2005/2008 의 Profiler 덕을 많이 봤습니다.
Visual Studio 2005의 Profiler 로 성능에 문제가 있는 함수를 하나씩 파고 들면서 거기서 가장 많은 시간을 소비하거나, 쓸데 없이 많이 호출되는 함수들을 개선해 나가는 식으로 진행했는데요.
작업을 하다 보니 STL 의 map 을 이용해서 insert 하고, find 하는 코드들이 시간 소비가 많다는 것을 확인했습니다.
워낙 많이 호출되다 보니 그렇겠거니 하고 일단 쉽게 수정 가능한 다른 부분들을 먼저 수정해서 큰 고비는 넘겼습니다.
그래도 마음 속으로는 계속 map 에 대한 성능부분이 마음에 걸려 Visual Studio 에서 제공하는 다른 map 들과 간단하게 성능을 비교해 봤습니다.
성능 비교에 쓰인 3가지 클래스입니다.
std:map - C++ 표준에서 제공하는 일반적인 map, 바이너리 트리 검색
CAtlMap – ATL 에서 제공하는 map, 해시 검색
CRBMap – ATL 에서 제공하는 map, 바이너리 트리 검색
구조적으로 STL 의 map 과 ATL 의 CRBMap 는 비슷한 방식으로 가지고 있어 성능도 비슷할 것으로 보였지만, 실제 결과는 상당한 성능 차이가 있었습니다.
일단 대상으로 삼은 <Key, Value> 조합은 <LPCTSTR, TEST_DATA> 입니다.
문자열을 키로 삼아 원하는 구조체를 찾는 일반적인 방식을 사용했습니다.
참고로 TEST_DATA 구조체는
struct TEST_DATA
{
long lData;
wstring sData;
};
와 같이 임의로 정의했습니다.
테스트를 위해 사용할 오늘의 컨테이너들을 소개합니다.
map<LPCTSTR, TEST_DATA> m_mapData;
CAtlMap <LPCTSTR, TEST_DATA> m_atlmapData;
CRBMap <LPCTSTR, TEST_DATA> m_RBmapData;
Insert할 데이터의 공정성(?)을 위해 키가 되는 문자열은 일방향적인 삽입이 일어나지 않도록 하기 위해서 rand() 함수를 통해 문자열로 변환했습니다. 아래는 map 에 insert 를 위해서 vector 에 랜덤한 문자열을 만들어 저장하는 코드입니다. vector<> 에 저장된 데이터들은 순서대로 map 에 추가하는 방식을 사용했습니다.
for (int i = 0; i < MAX_ELEMENT; i++)
{
data.lData = rand();
_stprintf_s (szFormat, _countof(szFormat), _T("%010d%i\n"), data.lData,i);
data.sData = szFormat;
m_vtKey.push_back( make_pair(data.sData,data) );
}
1. Insert 비교
10,000 개 ~ 100,000 개의 TEST_DATA 를 Insert 하는 시험을 했습니다. 3가지 모두 동일한 조건으로 추가했고 추가를 위해 사용한 코드는 아래와 같습니다.
// STL MAP INSERT
DWORD dwStart = timeGetTime();
for (itV = m_vtKey.begin(); itV != m_vtKey.end(); ++itV)
{
m_mapData.insert(make_pair(itV->first.c_str(), itV->second) );
}
TRACE("%d,", timeGetTime() - dwStart);
// ATL MAP INSERT
dwStart = timeGetTime();
for (itV = m_vtKey.begin(); itV != m_vtKey.end(); ++itV)
{
m_atlmapData.SetAt(itV->first.c_str(), itV->second);
}
TRACE("%d,", timeGetTime() - dwStart);
// RBMAP INSERT
dwStart = timeGetTime();
for (itV = m_vtKey.begin(); itV != m_vtKey.end(); ++itV)
{
m_RBmapData.SetAt(itV->first.c_str(), itV->second);
}
TRACE("%d,", timeGetTime() - dwStart);
위와 같은 코드로 데이터를 10,000 ~ 100,0000 개까지 map 에 Insert 하는 동안 걸린 시간은 아래 차트와 같았습니다.
X 축은 Insert 횟수를, Y 축은 X 축의 횟수 만큼 Insert를 했을 때 걸린 시간을 의미합니다.
그림을 대충 봐도 알 수 있듯이 STL map 과 CAtlMap 의 Insert 시간은 상당한 차이를 보입니다. 게다가 비슷한 방식의 CRBMap 과도 상당한 차이가 나는 것을 볼 수 있습니다. 이러한 차이는 데이터가 늘어날 수록 극명해 짐을 알 수 있었습니다.
2. Find 비교
Insert 와 같은 방식으로 이번에는 각각의 map에 들어 있는 모든 항목을 하나씩 찾는데 걸린 시간을 비교했습니다.
비교에 사용된 코드입니다.
// STL MAP FIND
dwStart = timeGetTime();
// find 성능분석
for (itV = m_vtKey.begin(); itV != m_vtKey.end(); ++itV)
{
it = m_mapData.find(itV->first.c_str());
if (it == m_mapData.end())
TRACE(_T("%s\n"), itV->first.c_str());
}
TRACE("%d,", timeGetTime() - dwStart);
// ATL MAP FIND
dwStart = timeGetTime();
for (itV = m_vtKey.begin(); itV != m_vtKey.end(); ++itV)
{
pPair1 = m_atlmapData.Lookup(itV->first.c_str());
if (pPair1 == NULL)
TRACE(_T("%s\n"), itV->first.c_str());
}
TRACE("%d,", timeGetTime() - dwStart);
// RBMAP FIND
dwStart = timeGetTime();
for (itV = m_vtKey.begin(); itV != m_vtKey.end(); ++itV)
{
pPair2 = m_RBmapData.Lookup(itV->first.c_str());
if (pPair2 == NULL)
TRACE(_T("%s\n"), itV->first.c_str());
}
TRACE("%d,", timeGetTime() - dwStart);
비교 차트입니다. 바이너리 검색보다 역시 해시 검색에서 확실히 더 나은 성능을 보여주는 것을 알 수 있습니다. 그리고 조금 놀란 부분은 같은 바이너리 검색으로 알고 있는 STL 의 map 과 CRBMap 이 상당한 성능 차이를 보인다는 점이었습니다. 사실 1회성인 insert 에서 보다, 그 빈도 수가 훨씬 많은 편인 find 함수에서 이 정도의 격차가 난다는것만으로도 STL::map 구현에서 성능적인 문제들이 많이 간과되었다고 밖에 생각할 수가 없었습니다. (물론 Visual Studio 2008 의 구현에 국한 된 테스트라 다른 컴파일러에 대한 얘기로 확대 해석 할 수는 없습니다)
3. Find & Erase 비교
일반적으로 Find 이후에 Erase 를 하는 경우가 많을 거라 생각되어서 Find 와 Erase 를 함께 사용하여 수행 시간을 측정했습니다.
// STL MAP ERASE
dwStart = timeGetTime();
for (itV = m_vtKey.begin(); itV != m_vtKey.end(); ++itV)
{
it = m_mapData.find(itV->first.c_str());
if (it != m_mapData.end())
m_mapData.erase(it);
}
TRACE("%d,", timeGetTime() - dwStart);
// ATL MAP ERASE
dwStart = timeGetTime();
for (itV = m_vtKey.begin(); itV != m_vtKey.end(); ++itV)
{
m_atlmapData.RemoveKey(itV->first.c_str());
}
TRACE("%d,", timeGetTime() - dwStart);
// RBMAP ERASE
dwStart = timeGetTime();
for (itV = m_vtKey.begin(); itV != m_vtKey.end(); ++itV)
{
m_RBmapData.RemoveKey(itV->first.c_str());
}
TRACE("%d", timeGetTime() - dwStart);
Erase 부분도 역시 CAtlMap 의 압승입니다. CRBMap 과 STL map 은 어느 정도 비슷한 시간을 보였습니다.
혹시나 키가 문자열이 아닌 숫자라면 어떨까요.
이번에는 키를 숫자로 설정해서 <long, TEST_DATA> 로 컨테이너를 만들어 비교해 봤습니다.
사용된 컨테이너들입니다.
map<long, TEST_DATA> m_mapData2;
CAtlMap <long, TEST_DATA> m_atlmapData2;
CRBMap <long, TEST_DATA> m_RBmapData2;
코드는 거의 중복이기 때문에 생략하겠습니다.
1. Insert
2. Find
3. Find & Erase
위 두 가지 예는 데이터가 너무 많은 감이 있어 데이터를 1,000 ~ 10,000 개 정도로 줄여서 비교해 봤습니다. <LPCTSTR, TEST_DATA> 를 사용했습니다.
1. insert
2. Find
3. Find & Erase
데이터가 적은 경우에도 STL 의 map 이 Insert 와 Find 에 가장 많은 시간이 소요됐고, Erase 부분에서는 오히려 RBMap 이 가장 많은 시간이 걸린 것으로 조사됐습니다.
그리 어렵지 않은 테스트로 얻은 교훈은
- Visual Studio의 STL 의 map 구현이 뭔가 최적화 되지 않은 느낌입니다.
- 일반적인 경우에는 상관없겠으나 성능이 중요한 이슈인 프로그램에서는 CAtlMap 이나 다른 대안이 될만한 Map 을 찾거나, 가장 최적화된 Map 을 직접 만들어야 합니다.
그 동안 좀 무분별하게 STL MAP 을 선호하는 편이었는데 최근의 프로파일링 결과도 그렇고, 테스트 결과도 그렇고 좀 더 성능에 신경 써야겠다는 생각이 들었습니다.
조금 다른 얘기이긴 하지만 STL 자료구조의 성능 관련 아래 글도 읽어 볼만 합니다.
'프로그램' 카테고리의 다른 글
| 티스토리 블로그 댓글 알리미 티라이브 버전 0.3.2 (46) | 2009/04/28 |
|---|---|
| 티스토리 블로그 댓글 알리미 티라이브 패치 - 버전 0.3.1 (16) | 2009/01/02 |
| [C++] STL map 과 ATL 의 map 성능 비교 (18) | 2008/10/29 |
| [C/C++]자주 하는 실수 (15) | 2008/10/22 |
| [C++]소멸자에서 가상함수 호출하기 (8) | 2008/10/08 |
| [C++]포인터 Wrapping 클래스 만들기 (4) | 2008/09/09 |
-
오스카 2008/10/29 11:55
visual studio 2008(2005도 해당될 듯)의 stl 쪽엔 iterator 관련한 디버깅 코드가 있습니다. 이게 속도를 몇 배로 떨어지게 하는데요, 요걸 끄고 한 번 돌려보시면 어떨런지...
프로젝트 생성 시, debug/release 모두 디폴트로 해당 옵션이 켜져 있습니다. 끄실려면 #define _HAS_ITERATOR_DEBUGGING 0 을 추가하시면 됩니다. 그리고 release 테스트 하셔야 할 듯 하네요.
제 pc 에서는
MAX_ELEMENT : 100000
#TEST1
TestStlMap : 194
TestAtlMap : 62
TestRBMap : 80
#TEST2
TestStlMap : 191
TestAtlMap : 68
TestRBMap : 85
#TEST3
TestStlMap : 190
TestAtlMap : 67
TestRBMap : 85
#TEST4
TestStlMap : 195
TestAtlMap : 67
TestRBMap : 88
#TEST5
TestStlMap : 188
TestAtlMap : 71
TestRBMap : 88
결과적으로는 AtlMap이 가장 빠른가요? ㅎㅎ-
esstory
2008/10/29 13:03
오스카님 제주도에서 복귀하신건가요?.
_HAS_ITERATOR_DEBUGGING 는 처음 알았습니다. 예전에 ATL 쪽은 이런 define 들이 꽤 많았던걸로 기억하는데 요것도 확인해 볼게요 ^^ -
object 2008/10/29 13:22
AtlMap이 가장 빠를 수 밖에 없습니다. 아시다시피 해쉬테이블이라 O(1)이고 나머지 두 RBMap과 StlMap은 O(logn)이니 성능은 당연히 차이가 나겠죠. RBMap, STLmap은 키가 소팅이 되어있으니 소팅이 필요한 경우에만 사용해야합니다. 해쉬는 그렇지가 않죠. 그래서 굳이 AtlMap과 성능을 비교하는 건 목적이 다르기 때문에 정확한 비교는 아닙니다.
비교를 하려면 AtlMap과 STL extension에 있는 hash_map을 해야겠죠. 문제는 문제는 MSVC의 STL 구현이 너무 멍청한건지.. 너무 느리다는거죠. hash_map은 황당할 정도로 느렸습니다. -
esstory
2008/10/29 13:43
object 님
말씀대로 구현 방식이 너무 틀려서 비교 자체는 문제일 지 모르지만, 성능이 어떤가 확인차 만든 자료이니 이해를 ^^;
그리고 CRBMap 과 stl map 은 구현방식이 거의 같아야 하는데 insert/delete 에서도 상당한 차이가 나고 find 마자도 stl map 의 완패입니다. 어디서 싼 라이브러리를 사온건 아닌지. -
esstory
2008/10/29 14:07
오스카님 '_HAS_ITERATOR_DEBUGGING' 는 디버그 빌드에서만 해당하는거네요.
위 테스트는 디버그에서 대충 테스트 한거긴 한데 나머지 컨테이너도 같은 디버그 조건이라 생각하고 진행했더랬습니다 ㅎㅎ.
릴리즈에서도 한번 해 봐야겠네요. 하나 마나 결론은 같을 거 같습니다.
-
-
codewiz
2008/10/29 12:02
와 성능 차이가 엄청나군요... ㅎㅎ^^
오스카님이 지적하신 부분을 감안하더라도 꽤나 있네요. ㅋ~
저도 이제껏 무분별하게 썼던것 같은데 앞으로 주의해야 겠습니당...-
esstory
2008/10/29 13:04
저도 STL 전도사처럼 마구 기존 MFC 맵을 stl map 으로 바꾸고 있던 참인데 테스트 결과를 보고 많이 놀랬습니다.
저 땜에 전체 시스템을 느리게 한건 아닌가 싶기도 하구요
-
-
-
esstory
2008/12/02 08:48
우리가 알게 모르게 도움을 받고 편하게 쓰는 라이브러리들이 그 목적에 맞게 제대로 안 쓰이면 독이 되는거 같습니다. 프로파일링 꼭 해 보세요 ^^
-
-
-
esstory
2008/12/14 12:47
위에 object 님이 쓰신 내용처럼 해쉬 검색을 하는 map 과 바이너리 트리 검색을 하는 map 의 근본적인 차이도 있을거예요. 그래고 같은 바이너리 검색을 하는 컨테이너에서도 속도차이가 제법 있으니, 따져보고 써야 할 거 같습니다. 방문 감사합니다.^^
-
-
-
ummae
2009/08/21 10:11
음.. STL의 Map은 래드블랙 트리를 기본으로 구성되어 있는걸로 압니다. ATL의 Map이 해시 기반이라면 당연히 STL의 Map이 느릴 수 밖에 없습니다. 즉, 비교자체가 무의미 한 것으로 보입니다. ATL의 Map과 비교하려면 TR1의 UnorderedMap과 비교를 하는 것이 맞는것 같습니다. UnorderedMap은 ATL의 Map과 동일하게 해시 기반으로 구현되어있습니다.
-
esstory
2009/08/21 13:26
네 unmae 님 말씀이 정확하시구요.
일반적으로는 검색을 위해 당연하게 map을 남용하고 있어서 속도에 대해 정확히 알아 보고자 적은 글입니다.
그리고 VC 에 구현된 unordered map 도 성능이 많이 안좋아 구박을 많이 받고 있는걸로 압니다.
댓글 감사합니다
-
-
까마귀 2011/01/27 16:32
CATLMap 의 경우 해쉬다 보니 키에 문자열이 들어가지 않네요. -.-
테스트 코드를 보면 그냥 포인터 비교로 끝나버리게 되니 일반적으로 만들경우 정상적으로 find가 되지 않는군요.
속도가 느려서 변경해봤다가. 조금 탈력입니다.-
esstory
2011/01/27 16:45
키로 CString 을 사용할 수 있습니다. 아래 정도 코드일거예요
CAtlMap<CString, 구조체, CStringElementTraits<CString>>
-
http://eslife.tistory.com/trackback/325
- [펌] STL map 과 ATL 의 map 성능 비교 // standyhon님의블로그 2011/01/24 06:49 [Del]
- STL의 map 자료구조는 생각보다 느리다?! // don' care about rits' taskin' 2011/12/15 09:12 [Del]








