2014-11-09 305 views
1

考慮下面的代碼查找嵌套升壓的multi_index_container

struct VersionData 
{ 
    VersionData(); 
    VersionData(VersionData&& rhs); 
    int  m_versionId; 
    int  m_weight; 
    int  m_pId; 
    bool m_hdi; 
}; 

struct VersionId{}; 

typedef boost::multi_index_container< 
    VersionData, 
    bmi::indexed_by< 
     bmi::ordered_non_unique< 
      bmi::tag<VersionId>, 
      bmi::member<VersionData, int, &VersionData::m_versionId> 
     > 
    > 
> VersionDataContainer; 

struct VersionsData 
{ 
    VersionsData(); 
    VersionsData(VersionsData&& rhs); 
    int m_sdGroupId; 
    int m_retId; 
    VersionDataContainer m_versionData; 
}; 

struct mvKey{}; 

typedef boost::multi_index_container< 
    VersionsData, 
    bmi::indexed_by< 
    bmi::ordered_unique< 
      bmi::tag<mvKey>, 
      bmi::composite_key< 
       VersionsData, 
       bmi::member<VersionsData,int, &VersionsData::m_subdeliveryGroupId>, 
       bmi::member<VersionsData,int, &VersionsData::m_retargetingId> 
      > 
     > 
    > 
> mvDataContainer; 

mvDataContainer m_data; 

的意圖是利用mvDataContainer但在某些情況下,我需要VersionData所有VersionsData查找組合鍵來查找。類似於m_data.get < mvKey> .equal_range(make_tuple(ignore,ignore,ignore))。get < VersionId> .equal_range(123456);
第一個問題,它是可以實現的嗎?
其次,這是使用嵌套multi_index_containers的正確方法嗎?任何性能影響/收益?

+0

這似乎不可能。你爲什麼不合並這些結構並製作一個具有多個索引的單一MIC?這是MIC的目標,而這似乎解決了你的問題。 – 2014-11-09 10:57:01

+0

@IgorR。剛剛在4小時前放棄了MIC。查找過程花了很長時間,而且在查找結果範圍上迭代了一些奇怪的原因。所以,爲了縮短查找時間,我使用層次結構,畢竟在扁平結構中查找速度較慢(恕我直言),因爲它必須搜索更多元素。同時我已經完成了for循環中嵌套MIC的查找,並觀察到性能下降。有一種感覺,我必須回到繪圖板的數據結構 – kreuzerkrieg 2014-11-09 11:12:18

回答

0

因此,我們確實,而是採用嵌套容器,像(live on Coliru

你可以/應該執行它作爲一個單一/表/(畢竟,這是與幾個指標表) :

Live On Coliru

#include <boost/multi_index_container.hpp> 
#include <boost/multi_index/ordered_index.hpp> 
#include <boost/multi_index/member.hpp> 
#include <boost/multi_index/composite_key.hpp> 

namespace bmi = boost::multi_index; 

struct VersionRecord { 
    int m_subdeliveryGroupId; 
    int m_retargetingId; 
    int m_versionId; 
    int m_weight; 
    int m_pId; 
    bool m_hdi; 

    friend std::ostream& operator<<(std::ostream& os, VersionRecord const& record) { 
     return os << "{ " << record.m_subdeliveryGroupId << " " << record.m_retargetingId << " " 
      << record.m_versionId << " " << record.m_weight << " " << record.m_pId << " " << record.m_hdi << " }"; 
    } 
}; 

typedef boost::multi_index_container< 
    VersionRecord, 
    bmi::indexed_by< 
     bmi::ordered_unique< 
      bmi::tag<struct mvKey>, 
      bmi::composite_key< 
       VersionRecord, 
       bmi::member<VersionRecord,int, &VersionRecord::m_subdeliveryGroupId>, 
       bmi::member<VersionRecord,int, &VersionRecord::m_retargetingId> 
      > 
     >, 
     bmi::ordered_non_unique< 
      bmi::tag<struct VersionId>, 
      bmi::member<VersionRecord, int, &VersionRecord::m_versionId> 
     > 
    > 
> VersionTable; 

#include <iostream> 
#include <boost/range/iterator_range.hpp> 

int main() { 

    auto table = VersionTable { 
     { 21, 10,    1, 100, 123, false }, 
     { 9, 27,    2, 90, 123, false }, 
     { 12, 25,    3, 110, 123, true }, 
     { 10, 33, /*version 8:*/ 8, 80, 123, false }, 
     { 4, 38,    5, 101, 124, false }, 
     { 33, 7,     6, 91, 124, false }, 
     { 34, 27,    7, 111, 124, true }, 
     { 9, 11, /*version 8:*/ 8, 81, 124, false }, 
     { 0, 12,    9, 99, 125, false }, 
     { 35, 39, /*version 8:*/ 8, 89, 125, false }, 
     { 15, 15,    11, 109, 125, true }, 
     { 25, 32, /*version 8:*/ 8, 79, 125, false }, 
    }; 

    // debug table output 
    assert(table.size()==12); 
    for (auto& record : table) std::cout << record << "\n"; 

    // so now you can do: 
    auto& idx = table.get<VersionId>(); 

    std::cout << "---\nQuerying for version id 8:\n"; 
    for (auto& record : boost::make_iterator_range(idx.equal_range(8))) 
     std::cout << record << "\n"; 
} 

它打印:

{ 0 12 9 99 125 0 } 
{ 4 38 5 101 124 0 } 
{ 9 11 8 81 124 0 } 
{ 9 27 2 90 123 0 } 
{ 10 33 8 80 123 0 } 
{ 12 25 3 110 123 1 } 
{ 15 15 11 109 125 1 } 
{ 21 10 1 100 123 0 } 
{ 25 32 8 79 125 0 } 
{ 33 7 6 91 124 0 } 
{ 34 27 7 111 124 1 } 
{ 35 39 8 89 125 0 } 
--- 
Querying for version id 8: 
{ 25 32 8 79 125 0 } 
{ 35 39 8 89 125 0 } 
{ 10 33 8 80 123 0 } 
{ 9 11 8 81 124 0 } 

或者,您可以bolt an intrusive container上的VersionsData記錄頂部。然而,這會使設計複雜化(您必須使用auto_unlink節點掛鉤(犧牲線程安全控制),或者您必須確保容器始終保持同步。

+0

添加Coliru演示,另一個答案顯示 - 套裝Boost Intrusive'multiset'方法 – sehe 2014-11-10 01:11:18

+0

將檢查侵入性,不熟悉它。但是,您不必要地將「主鍵」 - subdeliverygroupid和retargetingid相乘。如果現實生活中的PK稍微複雜一些幷包含字符串會怎樣?並且VersionsData下的VersionData數量可能高達50萬個條目?看起來像是巨大的內存浪費(其中我不太在意),並且通過使用扁平結構可以使索引變大,從而使查找時間更長。正確?無論如何,我已經測試了兩種方法,看起來對我來說都太慢了。 – kreuzerkrieg 2014-11-10 05:27:37

+0

@ kreuzerkrieg如果它們都很慢,測試負荷是多少?你能做到SSCCE嗎?無論如何,這是一個帶有非常昂貴的'ExpensiveCommonVersion'的版本,它實際上並沒有重複公共數據,同時仍然像單個表一樣,在語義上:** [Live On Coliru](http://coliru.stacked-crooked。 COM /一個/ 4574bfc8af2e21da)**。 _(請注意,我禁用了鎖定,如果不介意常用組保留以供重用,則甚至可以禁用該refcounting)。請注意,ExpensiveCommonVersion構造函數/析構函數僅運行兩次。 – sehe 2014-11-10 09:32:22

1

除了另一個建議使用單個容器的答案爲整個表,這裏的基礎上Boost Intrusive multiset

的想法看它Live On Coliru

#include <boost/multi_index_container.hpp> 
#include <boost/multi_index/ordered_index.hpp> 
#include <boost/multi_index/member.hpp> 
#include <boost/multi_index/composite_key.hpp> 

// for intrusive multiset 
#include <boost/intrusive/set.hpp> 
#include <boost/range/iterator_range.hpp> 
#include <iostream> 

namespace bmi = boost::multi_index; 
namespace bi = boost::intrusive; 

struct VersionData : bi::set_base_hook<bi::link_mode<bi::auto_unlink> > { 
    VersionData(int versionId, int weight=0, int pId=0, bool hdi=false) : 
     m_versionId(versionId), m_weight(weight), m_pId(pId), m_hdi(hdi) { } 

    int  m_versionId; 
    int  m_weight; 
    int  m_pId; 
    bool m_hdi; 

    friend std::ostream& operator<<(std::ostream& os, VersionData const& vd) { 
     return os << "{ " << vd.m_versionId << " " << vd.m_weight << " " << vd.m_pId << " " << vd.m_hdi << " }"; 
    } 

    struct ById { 
     bool operator()(VersionData const& a, VersionData const& b) const { return a.m_versionId < b.m_versionId; } 
    }; 
}; 

typedef bi::multiset<VersionData, bi::constant_time_size<false>, bi::compare<VersionData::ById> > VersionIndex; 

typedef boost::multi_index_container< 
    VersionData, 
    bmi::indexed_by< 
     bmi::ordered_non_unique< 
      bmi::tag<struct VersionId>, 
      bmi::member<VersionData, int, &VersionData::m_versionId> 
     > 
    > 
> VersionDataContainer; 

struct VersionsData 
{ 
    int m_subdeliveryGroupId; 
    int m_retargetingId; 

    VersionDataContainer m_versionData; 
}; 

typedef boost::multi_index_container< 
    VersionsData, 
    bmi::indexed_by< 
    bmi::ordered_unique< 
      bmi::tag<struct mvKey>, 
      bmi::composite_key< 
       VersionsData, 
       bmi::member<VersionsData,int, &VersionsData::m_subdeliveryGroupId>, 
       bmi::member<VersionsData,int, &VersionsData::m_retargetingId> 
      > 
     > 
    > 
> mvDataContainer; 

void insert(
     mvDataContainer& into, VersionIndex& global_version_index, 
     int subdeliveryGroupId, int retargetingId, int 
     versionId, int weight, int pId, bool hdi) 
{ 
    auto& mainIdx = into.get<mvKey>(); 
    auto insertion = mainIdx.insert(VersionsData { subdeliveryGroupId, retargetingId, VersionDataContainer {} }); 
    mainIdx.modify(insertion.first, [&](VersionsData& record) { 
      auto insertion = record.m_versionData.insert(VersionData { versionId, weight, pId, hdi }); 
      global_version_index.insert(const_cast<VersionData&>(*insertion.first)); 
    }); 
} 

int main() { 

    VersionIndex global_version_index; 
    mvDataContainer table; 

    insert(table, global_version_index, 21, 10,    1, 100, 123, false); 
    insert(table, global_version_index, 9, 27,    2, 90, 123, false); 
    insert(table, global_version_index, 12, 25,    3, 110, 123, true); 
    insert(table, global_version_index, 10, 33, /*version 8:*/ 8, 80, 123, false); 
    insert(table, global_version_index, 4, 38,    5, 101, 124, false); 
    insert(table, global_version_index, 33, 7,     6, 91, 124, false); 
    insert(table, global_version_index, 34, 27,    7, 111, 124, true); 
    insert(table, global_version_index, 9, 11, /*version 8:*/ 8, 81, 124, false); 
    insert(table, global_version_index, 0, 12,    9, 99, 125, false); 
    insert(table, global_version_index, 35, 39, /*version 8:*/ 8, 89, 125, false); 
    insert(table, global_version_index, 15, 15,    11, 109, 125, true); 
    insert(table, global_version_index, 25, 32, /*version 8:*/ 8, 79, 125, false); 

    // debug table output 
    assert(table.size()==12); 

    // so now you can do: 
    std::cout << "---\nQuerying for version id 8:\n"; 
    for (auto& record : boost::make_iterator_range(global_version_index.equal_range(8))) 
     std::cout << record << "\n"; 

    table.erase(table.find(boost::make_tuple(10,33))); // auto unlinks from global_version_index 

    std::cout << "---\nQuerying for version id 8:\n"; 
    for (auto& record : boost::make_iterator_range(global_version_index.equal_range(8))) 
     std::cout << record << "\n"; 
} 

打印:

--- 
Querying for version id 8: 
{ 8 80 123 0 } 
{ 8 81 124 0 } 
{ 8 89 125 0 } 
{ 8 79 125 0 } 
--- 
Querying for version id 8: 
{ 8 81 124 0 } 
{ 8 89 125 0 } 
{ 8 79 125 0 } 
+0

與往常一樣,來自@sehe的高質量答案:)
我必須先閱讀有關侵入式容器的內容。 – kreuzerkrieg 2014-11-10 05:18:05

+0

好的,得到了​​侵入性的東西。看起來不錯,但是它如何比基於二進制搜索中log(n)複雜度的排序向量定製的數據結構更快?他們聲稱具有更好的數據引用的局部性,同樣矢量賦予優秀的局部性,因爲它在內存中是連續的 – kreuzerkrieg 2014-11-10 10:08:02

+1

一般來說,是的。量身定製的矢量永遠不會給你不同的索引(因爲你不能讓它們同時在多個方向上排序)。無論如何,已經將MIC設置爲flat_multimap。這仍然會給你帶來如何索引的問題(也可以看看Boost PropertyMap,IYAM)。我認爲這個入侵鉤子接近「量身定做的數據結構」。根據您的精確訪問模式,您可能會從單獨的指向元素的向量中獲得更好的性能。 (配置文件!並且非常厭倦於引入複雜性,您無法證明您需要) – sehe 2014-11-10 10:12:38

0

這不是我最初問的確切答案,但是由於性能問題被提及,並且根據與@sehe的討論,這就是我發現的。使用boost :: flyweight
2)使用MIC而不是定製的容器,MIC在搜索簡單索引時可能會稍微慢一點(取決於測試場景),因此,但一旦你使用複合鍵(併爲你的定製數據結構實現類似的行爲),它比定製的DS稍快,速度也更快。我之前使用的定製速度更快的語句是錯誤的,因爲我使用的是來自boost 1.52的MIC,看起來像使用帶有字符串的組合鍵(比沒有字符串的組合鍵慢5個數量級)時出現錯誤。當切換到1.57時,一切開始按預期工作。

Tests on Coliru
有一個很好的索引,傢伙!:)

+0

毫米。有趣。雖然在公平性方面''但是一旦你使用複合鍵(並且爲你定製的數據結構實現類似的行爲),它會比定製的DS'稍快到明顯更快 - 我會說你做錯了這完全是關於方便和缺乏wheel-reinvention(so:software stability) – sehe 2014-12-11 11:27:16

+0

同意,我討厭重新發明輪子,因爲輪子提供圖書館多年的工作,並且它會比我做得更好。方便也很重要,甚至可能是最重要的(可維護性),但是當量身定做的DS足夠方便時,有一些明顯而簡單的情況。這個項目開始的過程很簡單,當它變得更復雜時,我已經轉向MIC,但是隻有在我確信它能提供預期的性能之後。如果MIC不提供它,我會犧牲方便和重新發明車輪 – kreuzerkrieg 2014-12-11 11:39:05