2014-11-09 60 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