在嵌套的boost multi_index_container上查找

时间:2014-11-09 06:09:55

标签: c++ boost data-structures boost-multi-index

考虑以下代码

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中的复合键进行查找,但在某些情况下,我需要在所有VersionsData中查找VersionData。类似于m_data.get&lt; mvKey &gt; .equal_range(make_tuple(忽略,忽略,忽略))。get&lt; VersionId &gt; .equal_range(123456);
第一个问题,是否可以实现?
其次,这是使用嵌套multi_index_containers的正确方法吗?任何绩效影响/收益?

3 个答案:

答案 0 :(得分: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 }

答案 1 :(得分:0)

确实,不是像那样使用嵌套容器( live on Coliru

您可以/应该将其实现为单个/ table /(毕竟,这是一个包含多个索引的表):

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 }

或者,您可以在VersionsData记录之上 bolt an intrusive container 。但是,这会使设计复杂化(您必须使用auto_unlink节点挂钩(牺牲线程安全控制),或者您必须确保容器始终处于同步状态。

答案 2 :(得分:0)

这不是我最初提出的确切答案,但由于提到了性能问题,并且根据与@sehe的讨论,这就是我找到的。
1)使用扁平结构,使用boost :: flyweight
可以节省相同键上的浪费内存 2)使用MIC而不是定制容器,在搜索简单索引时MIC可能稍微慢一点(取决于测试场景),但是一旦你使用复合键(并为你的定制数据结构实现类似的行为),它就会略微快于量身定制的DS
我之前的说法是定制一个更快是错误的,因为我使用了来自boost 1.52的MIC,看起来在使用带字符串的复合键时有一个错误(比没有字符串的复合慢5个数量级)。当切换到1.57时,一切都按预期开始工作。

Tests on Coliru
伙计们有一个很好的索引! :)