将unique_ptr添加到向量中的类会导致3倍的加速



背景

我有一个大型图(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

最新更新