面對查找需求如何選擇容器
在寫代碼的時候,即使編程老手經常會遇到一個不知道如何抉擇的事情,面對查詢的需求如何選擇容器,容器的大小等因素也會困擾我們的選擇。爲什麼呢?新手面對查詢往往會直接選擇map,因爲map是內部是支持查詢函數的,但老手知道map是通過複雜性換取查詢的性能的(map的實現往往是紅黑樹),那如果要保存的數據個數不多呢,是否值得使用map這樣的容器呢?
最近兩天寫了幾行短小的代碼,針對這個問題進行了一測試,測試對vector,map,hash_map三種有代表性的容器進行了測試,測試容器的長度爲10,50,100,200,500,1000.測試的方法是使用int作爲測試對象,vector的查詢使用順序查找,map和hash_map的查詢使用容器的find函數。爲了能表現出測試結果,查詢測試了1000000次,開始時,查詢的數據我用rand函數產生隨機數作爲查詢對象,後來發現好像對查詢干擾過大,最後還是使用了順序數據作爲查詢對象。
由於測試結果數據枯燥,我在後面再附錄上測試的程序。每個測試都作了3次,取平均值給大家看看結果。測試使用的是STLPort,Visual Studio 2003的DEBUG版本。測試結果如下:
表1 各種容器的耗時
容器容納的尺寸 |
vector(ms) |
map(ms) |
hash_map(ms) |
10 |
578 |
1333 |
1333 |
20 |
1078 |
1562 |
1328 |
50 |
2520 |
1921 |
1328 |
100 |
4953 |
2176 |
1328 |
200 |
9806 |
2422 |
1322 |
500 |
24437 |
2739 |
1323 |
1000 |
48827 |
2942 |
1317 |
在容器的數量小於20個的時候,vector的還表現良好,即使有查詢的需求,也可以考慮直接選擇vector,但當容器的數據超過50個以後、,map的優勢就顯示出來了。而且當容器的容量繼續增大後,map的查詢速度增加也很平穩,當然這和map的實現是採取紅黑樹,查詢的時間複雜度是log2(N)有關。而且hash_map(unorder_map)表現只能用穩定高效形容。所以在大部分時候hash_map是我們的首選。
由於測試中有其他的干擾信息,所以測試數據不等於實際效率。這個數據只是一個參考數據。但是我們大致可以得到一些感覺,容器的尺寸小於20時,我們大可以選擇vector作爲容器。不用選擇更加複雜的容器。如果要容納的對象個數大於50而且需要查詢的時候,vector就不要選擇了。用更快的容器。對於map和hash_map,優先選擇hash_map。當然我說的是STLport的hash_map,不是微軟默認的。我在前面《慎用Visual Studio C++默認的hash_map》的文章說明過這個問題了。在此不復述了。
同時測試的是很我還測試一個其他問題,就是swtich case的性能,發現swtich case 的性能基本不會隨case的條件增加而增加耗時(懂彙編的要笑話我了,哈哈),而且非常非常非常非常快,所以,如果有時候你追求的極限性能的目標,而且可以用swtich case的時候,可以考慮用這個比較“醜陋”一點的寫法。
BTW:《c++編程規範》一書的第76條是,默認使用vector,否則使用其他容器。其中提到了選擇容器的3個原則:
1. 正確性事,簡單,清晰是第一位的
2. 在必要時考慮效率
3. 儘可能編寫事務安全的代碼。(注意插入和刪除的迭代器失效問題的影響)
附錄測試代碼如下,我懶得寫註釋了。偷懶。
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <stdlib.h>
#include <conio.h>
#include <iostream>
#include <fstream>
#include <memory.h>
#include <map>
#include <hash_map>
#include <ace/OS.h>
#include <ace/Time_Value.h>
#include <ace/SString.h>
#include <ace/Shared_Memory_MM.h>
#include <ace/Shared_Memory_SV.h>
#include <ace/Mem_Map.h>
#include <ace/SV_Shared_Memory.h>
using namespace std;
const size_t TEST_NUMBER = 100000*10;
void test_findwith_container(size_t container_len)
{
vector<int> int_vector;
map<int,int> int_map;
hash_map<int,int> int_hash;
int_vector.resize(container_len);
int_hash.resize(container_len);
//
for(size_t i = 0;i<container_len;i++)
{
int_vector[i]=i;
int_map[i] = i;
int_hash[i]=i;
}
ACE_Time_Value tvStart(0);
ACE_Time_Value tvEnd(0);
ACE_Time_Value tvPassTime(0);
tvStart = ACE_OS::gettimeofday();
for (size_t i= 0;i<TEST_NUMBER;++i)
{
int find_number = i%container_len;
//
for(size_t j = 0;j<container_len;j++)
{
if (int_vector[j]==find_number)
{
break;
}
}
}
tvEnd = ACE_OS::gettimeofday();
tvPassTime = tvEnd - tvStart;
cout<<"test vector gettimeofday :"<<tvPassTime.msec()<<" "<<endl;
tvStart = ACE_OS::gettimeofday();
for (size_t i= 0;i<TEST_NUMBER;++i)
{
int find_number = i%container_len;
int_map.find(find_number);
}
tvEnd = ACE_OS::gettimeofday();
tvPassTime = tvEnd - tvStart;
cout<<"test map gettimeofday :"<<tvPassTime.msec()<<" "<<endl;
tvStart = ACE_OS::gettimeofday();
for (size_t i= 0;i<TEST_NUMBER;++i)
{
int find_number = i%container_len;
int_hash.find(find_number);
}
tvEnd = ACE_OS::gettimeofday();
tvPassTime = tvEnd - tvStart;
cout<<"test hash gettimeofday :"<<tvPassTime.msec()<<" "<<endl;
}
//
int main(int argc, ACE_TCHAR* argv[])
{
for (int j=0;j<3;++j)
{
cout<<"container length = 10 "<<endl;
test_findwith_container(10);
cout<<"container length = 20 "<<endl;
test_findwith_container(20);
cout<<"container length = 50 "<<endl;
test_findwith_container(50);
cout<<"container length = 100 "<<endl;
test_findwith_container(100);
cout<<"container length = 200 "<<endl;
test_findwith_container(200);
cout<<"container length = 500 "<<endl;
test_findwith_container(500);
cout<<"container length = 1000 "<<endl;
test_findwith_container(1000);
}
_getch();
return 0;
}