如何使用 boost::object_pool 作为 boost::multi_index 分配器?
How to use boost::object_pool as a boost::multi_index allocator?
我正在尝试实现一个 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 声明中的分配器。
出于几个原因,您不能或不应该这样做。
首先,boost::object_pool
有一个性能问题:从中释放对象是O(N)。如果你想有效地做到这一点,你需要直接在 boost::pool
之上实现你自己的分配器。原因是 object_pool
使用了 "ordered free" 语义,您不希望它用于您的用例。有关此性能错误的更多详细信息,请参见此处:https://svn.boost.org/trac/boost/ticket/3789
其次,multi_index_container
实际上需要分配一些不同的东西,具体取决于您 select 的索引。能够分配 value_type
是不够的,它需要分配树节点等。这使得它完全不适合与池分配器一起使用,因为池分配器通常假定多个实例的单一类型(或至少单一尺寸)。
如果你想要最好的性能,你可能需要 "roll your own." 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?
您需要做的第一件事(在遇到性能瓶颈时总是如此)- 进行分析!
结果可能(而且很可能会)分配并不是你最糟糕的事情。
让我重申一下 Alexander 的建议,即您分析程序以了解问题的真正所在。我强烈怀疑 Boost.MultiIndex 本身会像你说的那么慢。以下程序测量了创建 order_sell
容器(没有 Boost.Pool)、用 10,000 个随机订单填充它所花费的时间 和 销毁它:
#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 使用何种后端,我们得到:
Insertion of 10,000 random orders plus container cleanup
0.00494657 seg.
我的机器(Intel Core i5-2520M @2.50GHz)中的 VS 2015 发布模式产生:
Insertion of 10,000 random orders plus container cleanup
0.00492825 seg.
因此,这比您报告的速度快 20 倍左右,而且我在测量中包括容器销毁和随机数生成。
一些额外的观察:
boost::object_pool
不提供标准库为与容器的互操作性指定的分配器接口。您可能想改用 boost::pool_allocator
(我已经玩了一会儿,但似乎并没有提高速度,但您的里程可能会有所不同)。
- John 的回答似乎暗示 Boost.MultiIndex 是次优的,因为它将节点与值或类似的东西分开分配。事实上,该库在内存分配方面尽可能高效,并且您无法使用 Boost.Intrusive 做得更好(实际上您可以得到相同的结果)。如果您对 Boost.MultiIndex 内部数据结构的外观感到好奇,请查看我的 this answer。特别是,对于具有散列索引和有序索引的
order_sell
容器,每个值都进入其自己的 one 节点,另外还有一个单独的所谓的桶数组(指针数组)长度与元素数量大致相同。对于基于节点的数据结构,没有比这更好的了(如果你想放弃迭代器的稳定性,你可以设计出更节省内存的方案)。
我正在尝试实现一个 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 声明中的分配器。
出于几个原因,您不能或不应该这样做。
首先,boost::object_pool
有一个性能问题:从中释放对象是O(N)。如果你想有效地做到这一点,你需要直接在 boost::pool
之上实现你自己的分配器。原因是 object_pool
使用了 "ordered free" 语义,您不希望它用于您的用例。有关此性能错误的更多详细信息,请参见此处:https://svn.boost.org/trac/boost/ticket/3789
其次,multi_index_container
实际上需要分配一些不同的东西,具体取决于您 select 的索引。能够分配 value_type
是不够的,它需要分配树节点等。这使得它完全不适合与池分配器一起使用,因为池分配器通常假定多个实例的单一类型(或至少单一尺寸)。
如果你想要最好的性能,你可能需要 "roll your own." 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?
您需要做的第一件事(在遇到性能瓶颈时总是如此)- 进行分析!
结果可能(而且很可能会)分配并不是你最糟糕的事情。
让我重申一下 Alexander 的建议,即您分析程序以了解问题的真正所在。我强烈怀疑 Boost.MultiIndex 本身会像你说的那么慢。以下程序测量了创建 order_sell
容器(没有 Boost.Pool)、用 10,000 个随机订单填充它所花费的时间 和 销毁它:
#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 使用何种后端,我们得到:
Insertion of 10,000 random orders plus container cleanup 0.00494657 seg.
我的机器(Intel Core i5-2520M @2.50GHz)中的 VS 2015 发布模式产生:
Insertion of 10,000 random orders plus container cleanup 0.00492825 seg.
因此,这比您报告的速度快 20 倍左右,而且我在测量中包括容器销毁和随机数生成。
一些额外的观察:
boost::object_pool
不提供标准库为与容器的互操作性指定的分配器接口。您可能想改用boost::pool_allocator
(我已经玩了一会儿,但似乎并没有提高速度,但您的里程可能会有所不同)。- John 的回答似乎暗示 Boost.MultiIndex 是次优的,因为它将节点与值或类似的东西分开分配。事实上,该库在内存分配方面尽可能高效,并且您无法使用 Boost.Intrusive 做得更好(实际上您可以得到相同的结果)。如果您对 Boost.MultiIndex 内部数据结构的外观感到好奇,请查看我的 this answer。特别是,对于具有散列索引和有序索引的
order_sell
容器,每个值都进入其自己的 one 节点,另外还有一个单独的所谓的桶数组(指针数组)长度与元素数量大致相同。对于基于节点的数据结构,没有比这更好的了(如果你想放弃迭代器的稳定性,你可以设计出更节省内存的方案)。