为什么使用weak_ptr进行调用会很慢?

7

我已经阅读了这个问题What's the performance penalty of weak_ptr?,但是我的测试结果与之不同。

我正在使用智能指针制作委托。下面的简单代码显示了weak_ptr的性能问题。有人可以告诉我原因吗?

#include <chrono>
#include <functional>
#include <iostream>
#include <memory>
#include <stdint.h>
#include <string>
#include <utility>

struct Foo
{
    Foo() : counter(0) { incrStep = 1;}

    void bar()
    {
        counter += incrStep;
    }

    virtual ~Foo()
    {
        std::cout << "End " << counter << std::endl;
    }
private:
    uint64_t counter;
    uint64_t incrStep;
};

void pf(const std::string &md, const std::function<void()> &g)
{
    const auto st = std::chrono::high_resolution_clock::now();
    g();
    const auto ft = std::chrono::high_resolution_clock::now();
    const auto del = std::chrono::duration_cast<std::chrono::milliseconds>(ft - st);
    std::cout << md << " \t: \t" << del.count() << std::endl;
}

And the test:

int main(int , char** )
{
    volatile size_t l = 1000000000ULL;
    size_t maxCounter = l;

    auto a = std::make_shared<Foo>();
    std::weak_ptr<Foo> wp = a;

    pf("call via raw ptr        ", [=](){
        for (size_t i = 0; i < maxCounter; ++i)
        {
            auto p = a.get();
            if (p)
            {
                p->bar();
            }
        }
    });

    pf("call via shared_ptr      ", [=](){
        for (size_t i = 0; i < maxCounter; ++i)
        {
            if (a)
            {
                a->bar();
            }
        }
    });

    pf("call via weak_ptr       ", [=](){
        std::shared_ptr<Foo> p;
        for (size_t i = 0; i < maxCounter; ++i)
        {
            p = wp.lock();
            if (p)
            {
                p->bar();
            }
        }
    });

    pf("call via shared_ptr copy", [=](){
        volatile std::shared_ptr<Foo> p1 = a;
        std::shared_ptr<Foo> p;
        for (size_t i = 0; i < maxCounter; ++i)
        {
            p = const_cast<std::shared_ptr<Foo>& >(p1);
            if (p)
            {
                p->bar();
            }
        }
    });

    pf("call via mem_fn         ", [=](){
        auto fff = std::mem_fn(&Foo::bar);
        for (size_t i = 0; i < maxCounter; ++i)
        {
            fff(a.get());
        }
    });

    return 0;
}

结果:

$ ./test
call via raw ptr            :   369
call via shared_ptr         :   302
call via weak_ptr           :   22663
call via shared_ptr copy    :   2171
call via mem_fn             :   2124
End 5000000000

从上面可以看出,weak_ptr在使用拷贝和std::mem_fn时比shared_ptr慢了10倍,在使用原始指针或者shared_ptr.get()时则慢了60倍。


7
你测试过优化后的构建吗? - TartanLlama
3
如果需要对一个weak_ptr进行线程安全的获取其所绑定的shared_ptr,这个过程是比较慢的。只有在无法确定所共享的对象是否已被销毁时,才应该使用weak_ptr。否则,请使用原始指针 - Galik
1
略微偏题:当我尝试使用gcc v5.3.0时,mem_fn部分根本不需要时间,这表明它已经将十亿次调用优化为简单的一次计数器增量。因此,我将计数器更改为“volatile”,然后raw_ptr和shared_ptr情况花费的时间与shared_ptr复制和mem_fn相同。我会看看你的编译器如何优化raw_ptr和shared_ptr情况。(使用v4.9时,我得到了类似于你的结果。) - rici
1
另外,使用clang-3.6(和libc ++)编译,结果为0、0、23178、20972、0。同样,将0变成合理的数字(2280、2406、23071、20110、2415)。但有趣的是,弱指针锁定和共享指针复制之间的差异消失了。 - rici
4
我认为你看到的是优化方面的问题。其中weak_ptr的情况是唯一一个函数被调用次数无法在编译时推断的情况。 - David Schwartz
显示剩余9条评论
1个回答

6

在尝试复制您的测试时,我意识到优化器可能会消除比它应该消除的更多内容。我的做法是利用随机数来打败过度优化,这些结果似乎很真实,std::weak_ptr 几乎比 std::shared_ptr 或其 原始指针 慢三倍。

我在每个测试中计算一个校验和以确保它们都在执行相同的工作:

#include <chrono>
#include <memory>
#include <random>
#include <vector>
#include <iomanip>
#include <iostream>

#define OUT(m) do{std::cout << m << '\n';}while(0)

class Timer
{
    using clock = std::chrono::steady_clock;
    using microseconds = std::chrono::microseconds;

    clock::time_point tsb;
    clock::time_point tse;

public:

    void start() { tsb = clock::now(); }
    void stop()  { tse = clock::now(); }
    void clear() { tsb = tse; }

    friend std::ostream& operator<<(std::ostream& o, const Timer& timer)
    {
        return o << timer.secs();
    }

    // return time difference in seconds
    double secs() const
    {
        if(tse <= tsb)
            return 0.0;

        auto d = std::chrono::duration_cast<microseconds>(tse - tsb);

        return double(d.count()) / 1000000.0;
    }
};

constexpr auto N = 100000000U;

int main()
{
    std::mt19937 rnd{std::random_device{}()};
    std::uniform_int_distribution<int> pick{0, 100};

    std::vector<int> random_ints;
    for(auto i = 0U; i < 1024; ++i)
        random_ints.push_back(pick(rnd));

    std::shared_ptr<int> sptr = std::make_shared<int>(std::rand() % 100);
    int* rptr = sptr.get();
    std::weak_ptr<int> wptr = sptr;

    Timer timer;

    unsigned sum = 0;

    sum = 0;
    timer.start();
    for(auto i = 0U; i < N; ++i)
    {
        sum += random_ints[i % random_ints.size()] * *sptr;
    }
    timer.stop();

    OUT("sptr: " << sum << " " << timer);

    sum = 0;
    timer.start();
    for(auto i = 0U; i < N; ++i)
    {
        sum += random_ints[i % random_ints.size()] * *rptr;
    }
    timer.stop();

    OUT("rptr: " << sum << " " << timer);

    sum = 0;
    timer.start();
    for(auto i = 0U; i < N; ++i)
    {
        sum += random_ints[i % random_ints.size()] * *wptr.lock();
    }
    timer.stop();

    OUT("wptr: " << sum << " " << timer);
}

编译器标志:

g++ -std=c++14 -O3 -g0 -D NDEBUG -o bin/timecpp src/timecpp.cpp

例子输出:

sptr: 1367265700 1.26869 // shared pointer
rptr: 1367265700 1.26435 // raw pointer
wptr: 1367265700 2.99008 // weak pointer

2
这并没有回答问题。我所理解的问题是“什么使weak_ptr变慢?”而不是“为什么[some code]不能显示weak_ptr很慢?” - Matthew James Briggs
4
我理解这个问题是在问“为什么我的特定测试很慢”,因为提问者链接了一个已经解释了为什么它慢的问题。但是提问者惊讶于他的测试结果表现得比之前更慢。他想知道原因。标题是“为什么通过weak_ptr调用如此缓慢?”(强调“如此”)。 - Galik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接