簡體   English   中英

set vs unordered_set用於最快的迭代

[英]set vs unordered_set for fastest iteration

在我的申請中,我有以下要求 -

  1. 數據結構將僅使用一些值(不是鍵/值對)填充一次。 值可能會重復,但我希望數據結構只存儲一次。

  2. 我將通過上面創建的數據結構的所有元素迭代100次。 元素在迭代中出現的順序並不重要。

約束1表明我將不得不使用set或unordered_set,因為數據不是鍵值對的形式。

現在set插入比unordered_set插入更昂貴,但數據結構只在我的程序開頭填充一次。

我相信決定因素是我可以多快地迭代數據結構的所有元素。 我不確定set或unordered_set對於此目的是否會更快。 我相信標准沒有提到這個事實,因為這個操作對於任一數據結構都是O(n)。 但我想知道iterator.next()哪個數據結構會更快。

有幾種方法。

  1. 對你的問題的評論建議保持一個std::unordered_set ,它具有最快的O(1)查找/插入和O(N)迭代(就像每個容器一樣)。 如果您的數據變化很大,或需要大量隨機查找,這可能是最快的。 測試
  2. 如果需要迭代100次而不進行中間插入,則可以對std::vector執行單個O(N)復制,並從連續的內存布局中獲取100次。 測試這是否比常規std::unordered_set更快。
  3. 如果在迭代之間進行少量中間插入,則可以使用專用向量。 如果你可以使用Boost.Container ,請嘗試使用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是哈希表 - 所以它們使用大量內存,迭代速度慢,引用局部性差。 如果你必須經常插入/刪除/查找數據, setunordered_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_boundupper_boundequal_range而不是findfind_if來使用排序數據的好處。

無序集使用散列表來提供近O(1)時間搜索。 這是通過使用鍵的散列來計算從數據集的開頭搜索的元素(鍵)的偏移量來完成的。 除非您的數據集很小(如char ),否則不同的鍵可能具有相同的哈希值(碰撞)。

為了最大限度地減少沖突,無序集必須保持數據存儲相當稀疏。 這意味着找到一個鍵最多是O(1)時間(除非發生碰撞)。

但是當迭代遍歷散列表時,我們的迭代器將在我們的數據存儲區中遇到大量未使用的空間,這會減慢迭代器對下一個元素的查找速度。 我們可以使用額外的指針鏈接散列表中的相鄰元素,但我不認為無序集合會這樣做。

鑒於上述情況,我建議您使用排序向量作為“集合”。 使用二分法,您可以在O(log n)時間搜索商店,並且遍歷列表是微不足道的。 向量具有額外的優勢,即內存是連續的,因此您不太可能遇到緩存未命中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM