2016-03-25 8 views
3

我開發一個時間關鍵型應用程序和最佳集裝箱正在尋找最好的容器來處理以下類型的元素的集合:有序元素

class Element 
{ 
    int weight; 
    Data data; 
}; 

考慮到時間我的應用程序的關鍵步驟,在一個獨特的線程週期性地執行,有以下幾種:

  • Element具有最低weight從容器中取出,並data被處理;
  • 數字n> = 0的新Element,隨機(*)weight,插入到容器中。

容器的一些Element可能具有相同的重量。隨時在容器中的元素總數很高,平均幾乎平穩(幾十萬)。上述提取/加工/插入序列所需的時間必須儘可能低。 (注(*):新的重量實際上是從數據計算,而是被視爲隨機這裏簡化)

不同STL容器的一些搜索和嘗試後,我結束了使用的std :: multiset的容器,它執行速度比訂購std :: vector快16倍,比訂購std:列表快16倍。但是,我想知道如果我能夠實現更好的性能,考慮到我的應用程序的瓶頸仍然在提取/插入操作中。

請注意,雖然我只嘗試了有序的容器,但在我的要求中沒有提到「有序容器」。我不需要在容器中訂購Element,我只需要儘可能快地執行「提取最低加權元素」/「插入新元素」操作。我不限於STL容器,如果適用,可以進行升級或其他任何實現。

感謝您的幫助。

+1

你能詳細說明「時間關鍵」部分嗎?它只是需要儘快發生的結構集合嗎?將它們存儲在容器中?他們的處理?混合物?還有別的嗎? –

+0

@Joachim:應用程序對時間至關重要,因爲整個提取/過程/插入序列必須在有限且儘可能短的時間內執行。 – shrike

回答

3

我不需要在容器中排序元素,我只需要儘可能快地執行「提取最低加權元素」/「插入新元素」操作。

那麼你應該嘗試priority_queue<T>,或在vector<T>使用make_heap/push_heap/pop_heap操作。

由於您正在尋找最小堆,而不是最大堆,因此您需要提供一個自定義比較器,以相反的順序對您的Element對象進行排序。

+1

我嘗試使用std :: priority_queue,遵循你的第一個命題。使用這個容器,提取/插入操作的執行時間是std :: multiset解決方案的兩倍。這很棒 !我也嘗試了你的第二個命題(vector/make_heap),並且我的性能比priority_queue略低(並且代碼不太容易編寫)。首選首選命題。非常感謝您的回答以及我的問題的出色解決方案。 – shrike

0

嘗試任一這些:上述

std::map<int,std::vector<Data>> 

std::unordered_map<int,std::vector<Data>> 

int是重量。

這些都有不同的速度查找,刪除和添加取決於許多不同的因素,如元素是否存在或不存在。 (如果有的話,unordered_map .find更快,否則,地圖。發現是更快)

+0

據我所知,std :: map的實現通常使用二叉樹,就像std :: multiset一樣。你確定我可以期待使用它顯着提高性能嗎?而對於std:unordered_map,我想這可能是一個解決方案,如果權重在提取之前已知,事實並非如此。 – shrike

+0

你打算如何找到重量最輕的元素? – TemplateRex

+0

@TemplateRex:我想你的問題是針對Cmor​​aski ... – shrike

1

我認爲,在STL內,懶惰std::vector會給最好的結果。

建議的僞代碼可能看起來像:

  • 佈設回新元素在向量
  • 結束只有當你想最小的元素,數組進行排序並獲得第一個元素

通過這種方式,您可以獲得延遲的插入時間vector,相對較少的內存分配量和良好的緩存局部性。

+0

那麼你的應用程序的瓶頸是什麼? –

+0

我很困惑,但在測試過程中我犯了一個錯誤:我在測試dasblinkenlight的第二個解決方案(#define錯誤)時認爲測試了您的解決方案。使用你的懶惰矢量解決方案,我確實得到了戲劇性的糟糕表現。由此可見,性能損失既不在提取中,也不在插入操作中(排序似乎運行得相當快),但在流程操作本身中,通常需要10倍的時間。這樣的過程只執行計算並且不對容器執行任何操作(既不提取也不插入)。 – shrike

+0

如果你會過去所有你試過的東西,我們將會幫助你更多 –

1

考慮不同的候選人以及您的假設如何影響最終選擇是有益的。當您的要求發生變化時,切換容器會變得更加容易。

通常,尺寸N的容器具有其基本ACCES /修改操作大致3複雜類:(攤銷)O(1)O(log N)O(N)

第一要求(尋找最低權重元素)爲您大致有三種候選人O(1)的複雜性,以及一名候選人與O(N)複雜每件

  1. O(1)std::priority_queue<Element, LowestWeightCompare>

  2. O(1) for std::multiset<Element, LowestWeightCompare>

  3. O(1)boost::flat_multiset<Element, LowestWeightCompare>

  4. O(N)std::unordered_multiset<Element>

第二個要求(新元素的隨機插入)提供以下複雜每元件每個上述四種選擇的

  1. O(log N)std::priority_queue

  2. O(log N)std::multiset

  3. O(N)boost::flat_multiset

  4. 攤銷O(1)std::unordered_multiset

其中前三種選擇,boost::multiset應該由另外兩個主導爲大N。在剩下的兩個中,std::priority_queuestd::multiset更好的緩存行爲可能佔上風。但是:措施,措施,措施,但是。

它是模糊的先驗std::unorderd_multiset是否與其他三個競爭力。取決於隨機插入元件的數量n,每批的find(1)-insert(n)總成本將是爲O(N) search + O(n) insertionstd::unordered_multisetO(1) search + O(n log N) insertionstd::multiset。再次,測量,測量,測量

如何強大的是這些因素相對於你的要求?如果您必須在每批中找到k最低重量元素,則故事會發生如下變化。那麼你必須比較find(k)-insert(n)的成本。搜索成本將大致規模爲

  1. O(k log N)std::priority_queue
  2. O(1)std::multiset
  3. O(1)boost::flat_multiset
  4. O(k N)std::unordered_multiset

注意,一個priority_queue只能有效地訪問頂部元素,而不是它的k頂部元素,實際上並沒有調用pop(),每個調用的複雜度爲O(log N)。如果你希望你的代碼可能會從find(1)-insert(n)批處理模式更改爲find(k)-insert(n),那麼它可能是一個好主意,選擇std::multiset,或至少文檔什麼樣的接口的改變,將需要。

獎勵:兩全其美的?你也可能要實驗一下與Boost.MultiIndex和使用類似(查看文檔,以獲得語法正確的)

boost::multi_index< 
    Element, 
    indexed_by< 
     ordered_non_unique<member<Element, &Element::weight>, std::less<>>, 
     hashed_non_unique<> 
    > 
> 

上面的代碼將創建一個實現兩個指針結構跟蹤基於節點的容器這兩個按重量排序的Element也允許快速哈希插入。這將允許O(1)查找最低重量Element的同時還能夠n新元素O(n)隨機插入。

對於大型N,它應該規模比四個前面提到的容器好,但同樣,通過指針追成隨機內存可能會破壞在std::priority_queue它的理論優勢誘導適度N,緩存的效果。我有沒有提到措施,措施,措施的口頭禪?

+1

非常感謝你對我的問題和你提出的詳細解決方案的分析;我會嘗試'multi_index',因爲'N'實際上相當大(現在,'N'〜200k個元素幾乎是靜止的,我希望能夠管理更多)。只是關於我**的第一個要求的評論**:它不僅是**找到最低重量的元素,而且還**從容器中提取**,所以'O(log N)',如果我是沒有錯,爲'priority_queue'和'multi_set'(對於其他人,我不知道)。再一次,謝謝,當然,我在我的代碼中包含準確的時間**測量**。 – shrike