背景
我有一个大型图(100k个节点(,其中每个节点必须为每个传出边缘存储一点信息。我没有将其保存在std::vector<bool>
中,而是使用Boost 1.58中的dynamic_bitset
来执行逐位操作。每个节点还保留一个指向某个多态对象的指针。一个最小的例子是这样的,
struct Node
{
std::vector<size_t> succ;
boost::dynamic_bitset<> succ_flags;
std::unique_ptr<Object> data;
};
问题
考虑一下这个简单的基准测试程序,它创建并销毁一个图:
#include <vector>
#include <boost/dynamic_bitset.hpp>
#include <memory>
constexpr int N = 50000;
struct Node
{
std::vector<size_t> succ;
boost::dynamic_bitset<> succ_flags;
//std::unique_ptr<int> data;
Node(int i)
{
for (int j = i; j < N; j += i) succ.emplace_back(j);
succ_flags.resize(succ.size());
}
};
int main()
{
std::vector<Node> nodes;
for (int i = 1; i <= N; i++) nodes.emplace_back(i);
return 0;
}
在time
命令下运行,典型的结果是
real 0m0.055s
user 0m0.043s
sys 0m0.010s
然而,取消对unique_ptr
行的注释会产生类似的效果
real 0m0.017s
user 0m0.010s
sys 0m0.003s
结论:通过添加std::unique_ptr
数据成员使Node
更重会使std::vector
的执行速度快3倍以上!
问题
为什么会发生这种情况,这里是什么样的陷阱?
添加unique_ptr
数据成员会使Node
成为仅移动类型,并且vector
在增加其大小时会被迫移动现有元素。在原始示例中,vector
正在复制现有元素,因为默认的移动构造函数定义不是noexcept
(因为dynamic_bitset
移动构造函数不是noexcept
(。
您应该能够通过以下两种方式之一在不添加unique_ptr
的情况下复制更快的结果:
vector
中的reserve
房间
std::vector<Node> nodes;
nodes.reserve(N);
或者为Node
定义自己的noexcept
移动构造函数
Node(Node&& other) noexcept
: succ(std::move(other.succ))
, succ_flags(std::move(other.succ_flags))
{}
请注意,如果您提供了上面的移动构造函数,而dynamic_bitset
的移动构造函数实际上抛出了异常,则会调用std::terminate
。