查找嵌套的boost multi_index_container

Lookup on nested boost multi_index_container

本文关键字:index container multi boost 嵌套 查找      更新时间:2023-10-16

考虑以下代码

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(忽略,忽略,忽略)).get<VersionId.equal_range(123456)
第一个问题,它可以实现吗
其次,这是使用嵌套的multi_index_containers的正确方法吗?是否有任何性能影响/收益?

除了其他答案建议整个表使用单个容器外,以下是基于Boost Intrusive multiset的想法

查看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 }

事实上,与其使用嵌套容器,不如使用类似的容器(生活在Coliru上)

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

在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记录的顶部栓入一个侵入性容器。然而,这会使设计变得复杂(要么必须使用auto_unlink节点挂钩(牺牲线程安全控制),要么必须确保容器始终同步。

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

对Coliru的测试
有一个很好的索引,伙计们!:)