【问题标题】:How to use boost::object_pool as a boost::multi_index allocator?如何使用 boost::object_pool 作为 boost::multi_index 分配器?
【发布时间】:2016-05-08 02:36:45
【问题描述】:

我正在尝试实现 boost::multi_index 应用程序,但性能非常糟糕:插入 10,000 个对象几乎需要 0.1 秒,这是不可接受的。因此,当我查看文档并发现 boost::multi_index 可以接受内存分配器作为最后一个参数时,但是当我尝试实现自己时遇到了很多编译错误。请帮我改正。谢谢。

struct order
{
    unsigned int    id;
    unsigned int    quantity;
    double          price;
};

struct id{};
struct price{};

typedef multi_index_container<
  order,
  indexed_by<
    hashed_unique<
      tag<id>,  BOOST_MULTI_INDEX_MEMBER(order, unsigned int, id)>,
    ordered_non_unique<
      tag<price>,BOOST_MULTI_INDEX_MEMBER(order ,double, price),
        std::less<double> >
  >,
  boost::object_pool<order>
> order_sell; 

一般来说,编译器不喜欢将 boost::object_pool 的表达式作为 order_sell 声明中的分配器。

【问题讨论】:

    标签: c++ boost pool boost-multi-index


    【解决方案1】:

    让我重新陈述 Alexander 的建议,即您对程序进行概要分析,以便了解问题的真正所在。我强烈怀疑 Boost.MultiIndex 本身会像你说的那样慢。以下程序测量创建 order_sell 容器(没有 Boost.Pool)、用 10,000 个随机订单填充它并销毁它所花费的时间:

    Live Coliru Demo

    #include <algorithm>
    #include <array>
    #include <chrono>
    #include <numeric> 
    
    std::chrono::high_resolution_clock::time_point measure_start,measure_pause;
    
    template<typename F>
    double measure(F f)
    {
      using namespace std::chrono;
    
      static const int              num_trials=10;
      static const milliseconds     min_time_per_trial(200);
      std::array<double,num_trials> trials;
      volatile decltype(f())        res; /* to avoid optimizing f() away */
    
      for(int i=0;i<num_trials;++i){
        int                               runs=0;
        high_resolution_clock::time_point t2;
    
        measure_start=high_resolution_clock::now();
        do{
          res=f();
          ++runs;
          t2=high_resolution_clock::now();
        }while(t2-measure_start<min_time_per_trial);
        trials[i]=duration_cast<duration<double>>(t2-measure_start).count()/runs;
      }
      (void)res; /* var not used warn */
    
      std::sort(trials.begin(),trials.end());
      return std::accumulate(
        trials.begin()+2,trials.end()-2,0.0)/(trials.size()-4);
    }
    
    void pause_timing()
    {
      measure_pause=std::chrono::high_resolution_clock::now();
    }
    
    void resume_timing()
    {
      measure_start+=std::chrono::high_resolution_clock::now()-measure_pause;
    }
    
    #include <boost/multi_index_container.hpp>
    #include <boost/multi_index/hashed_index.hpp>
    #include <boost/multi_index/ordered_index.hpp>
    #include <boost/multi_index/member.hpp>
    
    using namespace boost::multi_index;
    
    struct order
    {
        unsigned int    id;
        unsigned int    quantity;
        double          price;
    };
    
    struct id{};
    struct price{};
    
    typedef multi_index_container<
      order,
      indexed_by<
        hashed_unique<
          tag<id>,BOOST_MULTI_INDEX_MEMBER(order, unsigned int, id)>,
        ordered_non_unique<
          tag<price>,BOOST_MULTI_INDEX_MEMBER(order ,double, price),
            std::less<double> >
      >
    > order_sell; 
    
    #include <iostream>
    #include <random>
    
    int main()
    {
      std::cout<<"Insertion of 10,000 random orders plus container cleanup\n";
      std::cout<<measure([](){
        order_sell os;
        std::mt19937                                gen{34862};
        std::uniform_int_distribution<unsigned int> uidist;
        std::uniform_real_distribution<double>      dbdist;
    
        for(unsigned int n=0;n<10000;++n){
          os.insert(order{uidist(gen),0,dbdist(gen)});
        }
        return os.size();
      })<<" seg.\n";
    }
    

    当在-O3 模式下使用 Coliru 使用的任何后端运行时,我们得到:

    插入 10,000 个随机订单和容器清理 0.00494657 段。

    我的机器(Intel Core i5-2520M @2.50GHz)中的 VS 2015 发布模式产生:

    插入 10,000 个随机订单和容器清理 0.00492825 段。

    因此,这比您报告的速度快 20 倍左右,并且我的测量中包括了容器销毁和随机数生成。

    一些额外的观察:

    • boost::object_pool 不提供标准库为与容器的互操作性而指定的分配器接口。您可能想改用boost::pool_allocator(我玩过它,但似乎并没有提高速度,但您的里程可能会有所不同)。
    • John 的回答似乎暗示 Boost.MultiIndex 是次优的,因为它将节点与值或类似的东西分开分配。事实上,该库在内存分配方面尽可能高效,而且您无法使用 Boost.Intrusive 做得更好(实际上,您可以获得相同的效果)。如果您对 Boost.MultiIndex 内部数据结构的外观感到好奇,请查看我的 this answer。特别是,对于带有散列索引和有序索引的 order_sell 容器,每个值都进入它自己的 one 节点,另外还有一个单独的所谓的桶数组(一个指针数组) 的长度与元素的数量大致相同。对于基于节点的数据结构,没有什么比这更好的了(如果您想摆脱迭代器的稳定性,您可以设计出更节省内存的方案)。

    【讨论】:

      【解决方案2】:

      您不能或不应该这样做有几个原因。

      首先,boost::object_pool 有一个性能问题:从中释放对象是 O(N)。如果您想有效地做到这一点,您需要直接在boost::pool 之上实现自己的分配器。原因是object_pool 使用“免费订购”语义,您的用例并不需要这种语义。有关此性能错误的更多详细信息,请参见此处:https://svn.boost.org/trac/boost/ticket/3789

      其次,multi_index_container 实际上需要分配一些不同的东西,具体取决于您选择的索引。能够分配value_type 是不够的,它需要分配树节点等。这使得它完全不适合与池分配器一起使用,因为池分配器通常假定单个类型的许多实例(或至少一个大小)。

      如果您想要获得最佳性能,您可能需要“自己动手”。 Boost MIC 和 Boost Pool 肯定不能一起玩。但另一个想法是使用性能更高的通用分配器,例如 tcmalloc:http://goog-perftools.sourceforge.net/doc/tcmalloc.html

      您可以考虑Boost Intrusive,它具有非常适合池分配的容器。您可以在 order 类型中添加挂钩,以便将它们存储在有序和无序映射中,然后您可以在 boost::pool 中分配订单。

      最后,既然您似乎在存储财务数据,您应该知道使用double 存储价格是危险的。有关更多信息,请参见此处:Why not use Double or Float to represent currency?

      【讨论】:

      • 我认为您对 Boost.MultIndex 的内部数据结构做出了一些不准确的假设。详情请参阅my answer
      • @JoaquínMLópezMuñoz:如果我这样说,你同意吗? "Boost MIC 分配你不知道类型和大小与value_type 不同的对象。"
      • 我同意后一种说法,但我不明白这意味着 lib 完全不适合与池分配器一起使用,或者 Boost MIC 和 Boost Pool 肯定不能很好地发挥作用一起:只需将boost::pool_allocator 插入order_sell 的定义并检查它是否有效(我做了)。相比之下,您还说 Boost.Intrusive 非常适合池分配:好吧,使用 unordered_set 钩子和 multiset 钩子布置基于 Boost.Intrusive 的容器将为您提供与基于 Boost.MultiIndex 的 order_sellboost::pool_allocator
      • 让我猜你的想法是任何非侵入式容器(例如 Boost-MultiIndex 以及所有基于节点的标准库容器,例如 std::setstd::unordered_set)都不适合池分配,因为内部节点的类型和大小与value_type 不一致。情况确实如此,但并不是真正的问题,因为容器内部通过allocator::rebind 获取了正确的分配器实例。我知道这可能会带来一些问题,因为您不能在代码中显式声明池或实例化,对吗?这是你的想法吗?
      • 对:例如,我创建了一个object_pool&lt;order&gt;,但 MIC 想分配sizeof(order) + 32,而我的池可能不支持。
      【解决方案3】:

      您需要做的第一件事(在性能瓶颈的情况下总是如此) - 是分析!

      事实证明,分配并不是你最糟糕的事情。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-01-23
        • 1970-01-01
        • 2016-03-23
        • 2011-03-29
        • 1970-01-01
        • 1970-01-01
        • 2015-05-04
        相关资源
        最近更新 更多