为啥通过weak_ptr调用这么慢?

Posted

技术标签:

【中文标题】为啥通过weak_ptr调用这么慢?【英文标题】:Why calling via weak_ptr is so slow?为什么通过weak_ptr调用这么慢? 【发布时间】:2016-02-01 15:49:42 【问题描述】:

我已阅读问题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;

还有测试:

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 复制时比 shared_ptr 慢 10 倍,std::mem_fn 比使用原始 ptr 或 shared_ptr.get() 慢 60 倍

【问题讨论】:

您是否测试了优化的构建? A weak_ptr 需要对 shared_ptr 进行线程安全的获取,这肯定会很慢。当您不知道共享对象是否已被销毁时,您应该只使用weak_ptr。否则使用 原始指针. 有点 OT:当我用 gcc v5.3.0 尝试这个时,mem_fn 部分完全没有时间,这表明它已经将千万次调用优化为一个简单的一次性增量柜台。所以我将计数器更改为volatile,然后 raw_ptr 和 shared_ptr 案例花费的时间与 shared_ptr 复制和 mem_fn 相同。我将看看您的编译器如何优化 raw_ptr 和 shared_ptr 案例。 (使用 v4.9,我得到的结果与您的相似。) 另外,用clang-3.6(和libc++)编译,结果是0、0、23178、20972、0。同样,把0变成合理的数字(2280、2406、23071、20110、 2415)。但有趣的是,锁定一个weak_ptr 和复制一个shared_ptr 之间的区别消失了。 我认为您看到了优化的怪癖。 weak_ptr 情况是唯一在编译时无法推断函数调用次数的情况。 【参考方案1】:

在尝试重现您的测试时,我意识到优化器可能会消除更多的东西。我所做的是利用随机数来避免过度优化,这些结果看起来很现实,std::weak_ptrstd::shared_ptr 或其原始指针慢了近三倍。

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

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

#define OUT(m) dostd::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 rndstd::random_device();
    std::uniform_int_distribution<int> pick0, 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

【讨论】:

这不能回答问题。我读到的问题是“是什么让weak_ptr变慢了?”不是“为什么 [某些代码] 不显示 weak_ptr 很慢?” @MatthewJamesBriggs 我读这个问题的方式是“为什么在我的特定测试中它很慢”,因为他链接到一个已经解释为什么它很慢的问题。但是 OP 对 他的 测试产生 甚至更慢 的性能感到惊讶。他想知道为什么。标题是“为什么通过weak_ptr 调用是那么 慢?” (强调所以

以上是关于为啥通过weak_ptr调用这么慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个涉及 list.index() 调用的 lambda 这么慢?

为啥 ProtoBuf 在第一次调用时这么慢,但在循环内部却非常快?

为啥与完全没有 JavaScript 相比,通过 AJAX 的 GET 请求替换 div 会导致我的网站变慢这么多?

从零开始写STL - 智能指针

为啥带有基于谓词的期望的 XCTest 这么慢?

为啥wordpress反应这么慢