[英]C++ std::map creation taking too long?
更新:
我正在開發一個性能非常關鍵的程序。 我有一個未排序的結構矢量。 我需要在這個向量中執行許多搜索操作。 所以我決定將矢量數據緩存到這樣的地圖中:
std::map<long, int> myMap;
for (int i = 0; i < myVector.size(); ++i)
{
const Type& theType = myVector[i];
myMap[theType.key] = i;
}
當我搜索地圖時,程序其余部分的結果要快得多。 然而,剩下的瓶頸是地圖本身的創建(平均花費大約0.8毫秒來插入約1,500個元素)。 我需要想辦法減少這個時間。 我只是插入一個long作為鍵和一個int作為值。 我不明白為什么這么長。
我的另一個想法是創建一個向量的副本(不能觸及原始的一個),並以某種方式執行比std :: sort更快的排序(它需要太長時間才能對它進行排序)。
編輯:
對不起大家。 我的意思是說我正在創建一個std :: map,其中鍵是long,值是int。 long值是struct的鍵值,int是向量中相應元素的索引。
此外,我做了一些調試,並意識到矢量根本沒有排序。 這完全是隨機的。 所以做一些像stable_sort這樣的事情就不會有用了。
另一個更新:
謝謝大家的回復。 我最終創建了一個對的向量(std :: vector of std :: pair(long,int))。 然后我按長值對矢量進行排序。 我創建了一個自定義比較器,僅查看該對的第一部分。 然后我使用lower_bound來搜索該對。 這就是我做到這一切的方式:
typedef std::pair<long,int> Key2VectorIndexPairT;
typedef std::vector<Key2VectorIndexPairT> Key2VectorIndexPairVectorT;
bool Key2VectorIndexPairComparator(const Key2VectorIndexPairT& pair1, const Key2VectorIndexPairT& pair2)
{
return pair1.first < pair2.first;
}
...
Key2VectorIndexPairVectorT sortedVector;
sortedVector.reserve(originalVector.capacity());
// Assume "original" vector contains unsorted elements.
for (int i = 0; i < originalVector.size(); ++i)
{
const TheStruct& theStruct = originalVector[i];
sortedVector.insert(Key2VectorIndexPairT(theStruct.key, i));
}
std::sort(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairComparator);
...
const long keyToSearchFor = 20;
const Key2VectorIndexPairVectorT::const_iterator cItorKey2VectorIndexPairVector = std::lower_bound(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairT(keyToSearchFor, 0 /* Provide dummy index value for search */), Key2VectorIndexPairComparator);
if (cItorKey2VectorIndexPairVector->first == keyToSearchFor)
{
const int vectorIndex = cItorKey2VectorIndexPairVector->second;
const TheStruct& theStruct = originalVector[vectorIndex];
// Now do whatever you want...
}
else
{
// Could not find element...
}
這為我帶來了適度的性能提升。 在我計算的總時間為3.75毫秒之前,現在它下降到2.5毫秒。
std :: map和std :: set都是在二叉樹上構建的,因此添加項會進行動態內存分配。 如果您的地圖基本上是靜態的(即在開始時初始化一次,然后很少或從未添加或刪除新項目),您可能最好使用排序向量和std :: lower_bound來使用二進制搜索查找項目。
地圖需要花費大量時間才有兩個原因
如果您只是將其作為一個批處理創建,那么使用自定義池分配器拋出整個映射可能是個好主意 - 例如,boost的pool_alloc 。 自定義分配器還可以應用優化,例如在地圖完全銷毀之前不實際釋放任何內存,等等。
由於您的鍵是整數,您可能還需要考慮基於基數樹 (在鍵的位上)編寫自己的容器。 這可能會顯着提高性能,但由於沒有STL實現,您可能需要編寫自己的。
如果您不需要對數據進行排序,請使用哈希表,例如std::unordered_map
; 這些可以避免分類數據所需的大量開銷,還可以減少所需的內存分配量。
最后,根據程序的整體設計,簡單地重復使用相同的地圖而不是一遍又一遍地重新創建它可能會有所幫助。 只需刪除並根據需要添加鍵,而不是構建新的矢量,然后構建新的地圖。 同樣,這可能不適用於您的程序,但如果是,它肯定會對您有所幫助。
我懷疑這是內存管理和樹重新平衡,這讓你付出了代價。
顯然,分析可能會幫助您找出問題所在。
我建議只需將你需要的long / int數據復制到另一個向量中,因為你說它幾乎已經排序了,所以在它上面使用stable_sort來完成排序。 然后使用lower_bound在已排序的向量中找到項目。
std :: find是一個線性掃描(必須是因為它適用於未排序的數據)。 如果您可以對數據進行排序(std :: sort guaranties n log(n)行為),那么您可以使用std :: binary_search來獲取log(n)搜索。 但正如其他人指出的那樣,復制時間可能就是問題所在。
如果鍵是實心的和短的,也許嘗試std::hash_map
。 來自MSDN的hash_map類頁面:
散列優於分類的主要優點是效率更高; 與用於分類技術的容器中的元素數量的對數成比例的時間相比,成功的散列以恆定的平均時間執行插入,刪除和查找。
如果您正在創建大型地圖並且正在將大量數據復制到其中,則地圖創建可能是性能瓶頸(在某種意義上它需要花費大量時間)。 您還使用了將元素插入到std :: map中的明顯(但次優)方式 - 如果您使用以下內容:
myMap.insert(std::make_pair(theType.key, theType));
這應該會提高插入速度,但是如果遇到重復鍵會導致行為略有改變 - 使用insert
會導致重復鍵的值被刪除,而使用您的方法時,將插入帶有重復鍵的最后一個元素進入地圖。
如果你的分析結果確定它是復制昂貴的元素,我還會考慮避免復制數據(例如通過存儲指向它的指針)。 但為此你必須分析代碼,IME猜測往往是錯誤的...
另外,作為旁注,您可能希望使用自定義比較器將數據存儲在std :: set中,因為您已經包含了密鑰。 然而,這並不會真正導致大的加速,因為在這種情況下構造一個集合可能與將其插入地圖一樣昂貴。
我不是C ++專家,但似乎你的問題源於復制Type
實例,而不是指向Type
實例的引用/指針。
std::map<Type> myMap; // <-- this is wrong, since std::map requires two template parameters, not one
如果你向地圖添加元素並且它們不是指針,那么我相信復制構造函數被調用,這肯定會導致大數據結構的延遲。 改為使用指針:
std::map<KeyType, ObjectType*> myMap;
此外,您的示例有點令人困惑,因為當您期望類型為Type
的值時,在地圖中“插入” int
類型的值。 我認為你應該分配對項目的引用,而不是索引。
myMap[theType.key] = &myVector[i];
我看你的例子越多,我就越困惑。 如果您正在使用std :: map,那么它應該采用兩種模板類型:
map<T1,T2> aMap;
那么你真正的映射是什么? map<Type, int>
還是別的什么?
您似乎使用Type.key
成員字段作為地圖的鍵(這是一個有效的想法),但除非鍵與Type
相同,否則您不能將它用作地圖的鍵。 那么key
是Type
一個實例??
此外,您將當前矢量索引映射到映射中的鍵,這表示您只需要向量的索引,以便以后可以快速訪問該索引位置。 那是你想做的嗎?
在閱讀完答案之后,您似乎正在使用std::map<long,int>
,在這種情況下,不會復制所涉及的結構。 此外,您不需要對向量中的對象進行本地引用。 如果您只需要訪問密鑰,請通過調用myVector[i].key
訪問它。
由於矢量已經部分排序,您可能希望創建一個輔助數組,引用原始矢量中元素的索引(索引)。 然后,您可以使用Timsort對輔助數組進行排序,這對於部分排序的數據(例如您的數據)具有良好的性能。
您從您提供的破碎示例中構建表的副本,而不僅僅是引用。
無論你在地圖中存儲什么,它依賴於你不改變矢量。 僅嘗試查找地圖。
typedef vector<Type> Stuff;
Stuff myVector;
typedef std::map<long, *Type> LookupMap;
LookupMap myMap;
LookupMap::iterator hint = myMap.begin();
for (Stuff::iterator it = myVector.begin(); myVector.end() != it; ++it)
{
hint = myMap.insert(hint, std::make_pair(it->key, &*it));
}
或者可能放下矢量並將其存儲在地圖中?
我想你還有其他一些問題。 創建一個1500 <long, int>
對的向量,並根據long對它進行排序應該花費相當少於0.8毫秒(至少假設我們正在談論一個相當現代的桌面/服務器類型處理器)。
為了試着了解我們應該在這里看到的內容,我做了一些測試代碼:
#include <vector>
#include <algorithm>
#include <time.h>
#include <iostream>
int main() {
const int size = 1500;
const int reps = 100;
std::vector<std::pair<long, int> > init;
std::vector<std::pair<long, int> > data;
long total = 0;
// Generate "original" array
for (int i=0; i<size; i++)
init.push_back(std::make_pair(rand(), i));
clock_t start = clock();
for (int i=0; i<reps; i++) {
// copy the original array
std::vector<std::pair<long, int> > data(init.begin(), init.end());
// sort the copy
std::sort(data.begin(), data.end());
// use data that depends on sort to prevent it being optimized away
total += data[10].first;
total += data[size-10].first;
}
clock_t stop = clock();
std::cout << "Ignore: " << total << "\n";
clock_t ticks = stop - start;
double seconds = ticks / (double)CLOCKS_PER_SEC;
double ms = seconds * 1000.0;
double ms_p_iter = ms / reps;
std::cout << ms_p_iter << " ms/iteration.";
return 0;
}
在我有點“尾隨”(〜5歲)的機器上運行這個,我的時間大約是0.1毫秒/迭代。 我希望在這里搜索(使用std::lower_bound
或std::upper_bound
)比在std::map
搜索要快一些(因為向量中的數據是連續分配的,我們可以期待更好的局部性引用,導致更好的緩存使用)。
謝謝大家的回復。 我最終創建了一個對的向量(std :: vector of std :: pair(long,int))。 然后我按長值對矢量進行排序。 我創建了一個自定義比較器,僅查看該對的第一部分。 然后我使用lower_bound來搜索該對。 這就是我做到這一切的方式:
typedef std::pair<long,int> Key2VectorIndexPairT;
typedef std::vector<Key2VectorIndexPairT> Key2VectorIndexPairVectorT;
bool Key2VectorIndexPairComparator(const Key2VectorIndexPairT& pair1, const Key2VectorIndexPairT& pair2)
{
return pair1.first < pair2.first;
}
...
Key2VectorIndexPairVectorT sortedVector;
sortedVector.reserve(originalVector.capacity());
// Assume "original" vector contains unsorted elements.
for (int i = 0; i < originalVector.size(); ++i)
{
const TheStruct& theStruct = originalVector[i];
sortedVector.insert(Key2VectorIndexPairT(theStruct.key, i));
}
std::sort(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairComparator);
...
const long keyToSearchFor = 20;
const Key2VectorIndexPairVectorT::const_iterator cItorKey2VectorIndexPairVector = std::lower_bound(sortedVector.begin(), sortedVector.end(), Key2VectorIndexPairT(keyToSearchFor, 0 /* Provide dummy index value for search */), Key2VectorIndexPairComparator);
if (cItorKey2VectorIndexPairVector->first == keyToSearchFor)
{
const int vectorIndex = cItorKey2VectorIndexPairVector->second;
const TheStruct& theStruct = originalVector[vectorIndex];
// Now do whatever you want...
}
else
{
// Could not find element...
}
這為我帶來了適度的性能提升。 在我計算的總時間為3.75毫秒之前,現在它下降到2.5毫秒。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.