【问题标题】:Boost multi-index container vs a multi-level mapping container based on std::unordered_map (map of maps)Boost 多索引容器与基于 std::unordered_map (map of maps) 的多级映射容器
【发布时间】:2014-12-15 13:37:47
【问题描述】:

我最近发现了 boost::multi_index_container,与我自己实现的基于多级映射的类似容器相比,我很好奇他的性能,并定义为:

typedef int      Data;
typedef uint64_t MainKey;
typedef uint64_t SecondaryKey;

typedef std::unordered_map<SecondaryKey, Data>       SecondaryMap;
typedef std::unordered_map<PrimaryKey, SecondaryMap> PrimaryMap;

键的顺序并不重要。快速查找很重要,为此我使用了类似的东西:

// find primaryKey=10 and secondaryKey=30
PrimaryMap m;
....
auto i1 = m.find( 10);
if ( i1 != m.end())
{
    auto& secondary = i1->second;
    auto i2 = secondary.find( 30);
    if ( i2 != secondary.end())
    {
        found = true;
        ....
    }
}

我想知道会是什么

  • 与我的实现最接近的 boost::multi_index_container 配置
  • 按主键和辅助键搜索的最快方式。

我已尝试配置模板,但不确定这是否是最佳解决方案:

struct RecordKey
{
    MainKey         mainKey;
    SecondaryKey    secondaryKey;

    RecordKey( const MainKey mainKey, SecondaryKey secondaryKey):
        mainKey( mainKey),
        secondaryKey( secondaryKey)
    {}
};

struct Record: public RecordKey
{
    Data data;

    Record( const MainKey mainKey = 0, const SecondaryKey secondaryKey = 0, const Data& data = 0):
        RecordKey( mainKey, secondaryKey),
        data( data)
    {}
};


struct MainKeyTag {};
struct SecondaryKeyTag {};
struct CompositeKeyTag {};

using boost::multi_index_container;
using namespace boost::multi_index;

typedef boost::multi_index_container<Record,
    indexed_by  <   /*random_access<>,*/
                    hashed_non_unique<tag<MainKeyTag>,      BOOST_MULTI_INDEX_MEMBER( RecordKey, MainKey, mainKey) >,
                    hashed_non_unique<tag<SecondaryKeyTag>, BOOST_MULTI_INDEX_MEMBER( RecordKey, SecondaryKey, secondaryKey) >,
                    hashed_unique<tag<CompositeKeyTag>,     composite_key<Record,   member<RecordKey, MainKey, &RecordKey::mainKey>,
                                                                                    member<RecordKey, SecondaryKey, &RecordKey::secondaryKey> > > > > RecordContainer;

【问题讨论】:

    标签: c++ boost std unordered-map boost-multi-index


    【解决方案1】:

    类似的情况,但是我的组不允许boost,所以通过以下方式实现,如果通过辅助键查找,只需要一个查找而不是两个:

        #include<map>
        using namespace std;
    
        template<class PrimaryKey, class SecondaryKey, class Data>
        class MyMultiIndexedMap
        {
            private:
            typedef map<PrimaryKey, Data*> PT;
            typedef map<SecondaryKey, Data*> ST;
    
            PT pri_data_;
            ST sec_data_;
    
            public:
            bool insert(PrimaryKey pk, SecondaryKey sk, Data *data);
            Data* findBySecondaryKey(SecondaryKey sk);       
            Data* findByPrimaryKey(PrimaryKey pk);    
        };
    

    【讨论】:

      【解决方案2】:

      多索引容器必须有3个索引:

      • 主键索引:hashed - 因为 std::unordered_map 已散列, non_unique - 因为多条记录可能有相同的键;
      • 辅助键索引:与主键相同;
      • 复合键的索引:hashed - 因为std::unordered_map 是散列的,unique - 因为元组(主键,辅助键)在映射的映射中是唯一的 结构。

      容器定义为:

      typedef boost::multi_index_container<Record, indexed_by <
          hashed_non_unique<tag<MainKeyTag>,      BOOST_MULTI_INDEX_MEMBER( RecordKey, MainKey, mainKey) >,
          hashed_non_unique<tag<SecondaryKeyTag>, BOOST_MULTI_INDEX_MEMBER( RecordKey, SecondaryKey, secondaryKey) >,
          hashed_unique<tag<CompositeKeyTag>,     
          composite_key<Record,
              member<RecordKey, MainKey, &RecordKey::mainKey>,
              member<RecordKey, SecondaryKey, &RecordKey::secondaryKey> > > > > RecordContainer;
      

      插入是:

      RecordContainer c;
      c.insert( Record( 10, 20, 0));
      c.insert( Record( 10, 30, 1));
      c.insert( Record( 10, 40, 2));
      

      查找是:

      auto& index = c.get<CompositeKeyTag>();
      auto pos = index.find( boost::make_tuple( 10, 30)); // don't use std::make_tuple!
      if ( pos != index.end())
      {
          found = true;
          data = pos->data;
      }
      

      【讨论】:

        【解决方案3】:

        通过在 BMI 中选择有序(而不是散列)索引,您也可以享用蛋糕。

        ordered复合索引有一个很好的特性,可以让你通过部分键进行查询,所以你只需要定义复合索引就可以通过主索引进行查询:

        typedef boost::multi_index_container<
            Record,
            indexed_by<
                ordered_non_unique< tag<CompositeKeyTag>, 
                composite_key<Record, 
                            member<RecordKey, MainKey, &RecordKey::mainKey>,
                            member<RecordKey, SecondaryKey, &RecordKey::secondaryKey>
            > > > > RecordContainer;
        

        现在您可以通过mainKey查询:

        range = index.equal_range(10);
        

        或者复合:

        range = index.equal_range(boost::make_tuple(10,30));
        

        背景:

        这是一个完整的演示Live On Coliru

        #include <boost/multi_index_container.hpp>
        #include <boost/multi_index/composite_key.hpp>
        #include <boost/multi_index/hashed_index.hpp>
        #include <boost/multi_index/ordered_index.hpp>
        #include <boost/multi_index/member.hpp>
        #include <boost/range/iterator_range.hpp>
        #include <iostream>
        
        using MainKey      = uint64_t;
        using SecondaryKey = uint64_t;
        using Data         = std::string;
        
        struct RecordKey
        {
            MainKey         mainKey;
            SecondaryKey    secondaryKey;
        
            RecordKey( const MainKey mainKey, SecondaryKey secondaryKey):
                mainKey( mainKey),
                secondaryKey( secondaryKey)
            {}
        };
        
        struct Record: public RecordKey
        {
            Data data;
        
            Record( const MainKey mainKey = 0, const SecondaryKey secondaryKey = 0, const Data& data = 0):
                RecordKey( mainKey, secondaryKey),
                data( data)
            {}
        
            friend std::ostream& operator<<(std::ostream& os, Record const& r) {
                return os << " Record[" << r.mainKey << ", " << r.secondaryKey << ", " << r.data << "]";
            }
        };
        
        struct MainKeyTag {};
        struct SecondaryKeyTag {};
        struct CompositeKeyTag {};
        
        using boost::multi_index_container;
        using namespace boost::multi_index;
        
        typedef boost::multi_index_container<
            Record,
            indexed_by<
                ordered_non_unique< tag<CompositeKeyTag>, 
                composite_key<Record, 
                            member<RecordKey, MainKey, &RecordKey::mainKey>,
                            member<RecordKey, SecondaryKey, &RecordKey::secondaryKey>
            > > > > RecordContainer;
        
        
        int main()
        {
            RecordContainer records;
            records.insert(Record(10, 20, "12"));
            records.insert(Record(10, 30, "13"));
            records.insert(Record(10, 30, "13 - need not be unique!"));
            records.insert(Record(30, 40, "34"));
            records.insert(Record(30, 50, "35"));
            records.insert(Record(50, 60, "56"));
            records.insert(Record(50, 70, "57"));
            records.insert(Record(70, 80, "78"));
        
            std::cout << "\nAll records:\n----------------------------------------------------------------------\n";
            for (auto const& r : records)
                std::cout << r << "\n";
        
            {
                std::cout << "\nAll records with (main) == (10):\n----------------------------------------------------------------------\n";
                auto& index = records.get<0>();
                auto range = index.equal_range(10);
                for (auto const& r : boost::make_iterator_range(range.first, range.second))
                    std::cout << r << "\n";
            }
        
            {
                std::cout << "\nAll records with (main,secondary) == (10,30):\n----------------------------------------------------------------------\n";
                auto& index = records.get<0>();
                auto range = index.equal_range(boost::make_tuple(10,30));
                for (auto const& r : boost::make_iterator_range(range.first, range.second))
                    std::cout << r << "\n";
            }
        }
        

        输出:

        All records:
        ----------------------------------------------------------------------
         Record[10, 20, 12]
         Record[10, 30, 13]
         Record[10, 30, 13 - need not be unique!]
         Record[30, 40, 34]
         Record[30, 50, 35]
         Record[50, 60, 56]
         Record[50, 70, 57]
         Record[70, 80, 78]
        
        All records with (main) == (10):
        ----------------------------------------------------------------------
         Record[10, 20, 12]
         Record[10, 30, 13]
         Record[10, 30, 13 - need not be unique!]
        
        All records with (main,secondary) == (10,30):
        ----------------------------------------------------------------------
         Record[10, 30, 13]
         Record[10, 30, 13 - need not be unique!]
        

        【讨论】:

        • 谢谢,但您是否建议有序变体与散列变体相比没有搜索性能损失?我想做性能测试来找出哪个更快,提升多索引容器或我自己的构造。我需要一个主键索引,一个辅助键索引和两个键的组合索引。恕我直言,散列应该比订购的更快。
        • @Flaviu 有序与散列始终取决于访问模式。但是看到您似乎认为 map-of-maps 适合您的访问模式,我非常确信有序复合是一个很好的匹配(这是您想要的,仅使用两个级联二进制搜索)。如果您/也/需要通过完全复合直接查找,您可以考虑添加散列复合索引以及。但是,请记住,这将对容器上的每个(键)修改操作产生不小的开销。
        • 我自己可以使用有序的 boost::multi_index_container 插入和搜索,但我正在寻找散列或 random_access 解决方案。散列复合键配置是否与我自己的查找结构最接近?
        • 取决于什么键。老化更多的索引也不是免费的。考虑外部助手并始终进行分析。
        • 我也在这里解决了类似的问题,在这里我有更多的上下文信息来提出额外的建议:stackoverflow.com/questions/26474577/… 特别是它可能会吸引你的随机访问方面的需求(你尚未澄清)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-05
        • 1970-01-01
        • 2012-03-17
        • 2010-12-23
        • 2012-05-21
        • 2014-12-26
        • 1970-01-01
        相关资源
        最近更新 更多