[英]set vs unordered_set for fastest iteration
在我的申請中,我有以下要求 -
數據結構將僅使用一些值(不是鍵/值對)填充一次。 值可能會重復,但我希望數據結構只存儲一次。
我將通過上面創建的數據結構的所有元素迭代100次。 元素在迭代中出現的順序並不重要。
約束1表明我將不得不使用set或unordered_set,因為數據不是鍵值對的形式。
現在set插入比unordered_set插入更昂貴,但數據結構只在我的程序開頭填充一次。
我相信決定因素是我可以多快地迭代數據結構的所有元素。 我不確定set或unordered_set對於此目的是否會更快。 我相信標准沒有提到這個事實,因為這個操作對於任一數據結構都是O(n)。 但我想知道iterator.next()哪個數據結構會更快。
有幾種方法。
std::unordered_set
,它具有最快的O(1)
查找/插入和O(N)
迭代(就像每個容器一樣)。 如果您的數據變化很大,或需要大量隨機查找,這可能是最快的。 但測試 。 std::vector
執行單個O(N)
復制,並從連續的內存布局中獲取100次。 測試這是否比常規std::unordered_set
更快。 boost::flat_set
,它提供了一個帶有std::vector
存儲后端的std::set
接口(即一個非常緩存和預取友好的連續內存布局)。 再次, 測試這是否能提高其他兩個解決方案的速度。 對於最后的解決方案,請參閱Boost文檔以獲取一些權衡(了解所有其他問題,如迭代器失效,移動語義和異常安全性):
Boost.Container flat_ [multi] map / set容器是基於Austern和Alexandrescu指南的基於有序矢量的關聯容器。 這些有序的向量容器最近也受益於向C ++添加移動語義,大大加快了插入和擦除時間。 扁平關聯容器具有以下屬性:
- 比標准關聯容器更快的查找
- 迭代比標准關聯容器快得多
- 小對象(如果使用shrink_to_fit,對於大對象)的內存消耗更少
- 改進的緩存性能(數據存儲在連續的內存中)
- 非穩定迭代器(插入和擦除元素時迭代器無效)
- 不能存儲不可復制和不可移動的值類型
- 比標准關聯容器更弱的異常安全性(復制/移動構造函數在擦除和插入時移動值時可以拋出)
- 比標准關聯容器更慢的插入和擦除 (特別是對於不可移動的類型)
注意 :使用更快的查找,這意味着flat_set
在連續內存上執行O(log N)
而不是對常規std::set
O(log N)
指針追蹤。 當然, std::unordered_set
執行O(1)
查找,對於大N
會更快。
我建議你使用set或unordered_set進行“過濾”,當你完成后,將數據移動到固定大小的向量
如果數據結構的構建不考慮性能問題(或者至少只是略微考慮),請考慮將數據保存到std::vector
:沒有什么可以打敗它。
為了加速數據結構的初始構建,您可能首先插入到std::unordered_set
或者至少使用一個用於在插入之前檢查是否存在。
在第二種情況下,它不需要包含元素,但可以包含例如索引。
std::vector<T> v;
auto h = [&v](size_t i){return std::hash<T>()(v[i]);};
auto c = [&v](size_t a, size_t b){return v[a] == v[b];};
std::unordered_set<size_t, decltype(h), decltype(c)> tester(0, h, c);
我強烈建議你不要在這種情況下使用。 set
是二叉樹, unordered_set
是哈希表 - 所以它們使用大量內存,迭代速度慢,引用局部性差。 如果你必須經常插入/刪除/查找數據, set
或unordered_set
不錯的選擇,但現在你只需要讀取,存儲,排序數據一次, 只需多次使用數據。
在這種情況下, 排序后的矢量可以是一個很好的選擇。 vector
是動態數組 ,因此開銷很低。
直接看看代碼。
std::vector<int> data;
int input;
for (int i = 0; i < 10; i++)
{
std::cin >> input;
data.push_back(input); // store data
}
std::sort(data.begin(), data.end()); // sort data
就這樣。 您的所有數據都准備好了。
如果您需要刪除像set
這樣的重復項,只需在排序后使用unique
- erase
。
data.erase(
std::unique(data.begin(), data.end()),
data.end()
);
請注意,您應該使用lower_bound
, upper_bound
和equal_range
而不是find
或find_if
來使用排序數據的好處。
無序集使用散列表來提供近O(1)時間搜索。 這是通過使用鍵的散列來計算從數據集的開頭搜索的元素(鍵)的偏移量來完成的。 除非您的數據集很小(如char
),否則不同的鍵可能具有相同的哈希值(碰撞)。
為了最大限度地減少沖突,無序集必須保持數據存儲相當稀疏。 這意味着找到一個鍵最多是O(1)時間(除非發生碰撞)。
但是當迭代遍歷散列表時,我們的迭代器將在我們的數據存儲區中遇到大量未使用的空間,這會減慢迭代器對下一個元素的查找速度。 我們可以使用額外的指針鏈接散列表中的相鄰元素,但我不認為無序集合會這樣做。
鑒於上述情況,我建議您使用排序向量作為“集合”。 使用二分法,您可以在O(log n)時間搜索商店,並且遍歷列表是微不足道的。 向量具有額外的優勢,即內存是連續的,因此您不太可能遇到緩存未命中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.