Urho vs stdlib Small Benchmark (VS2015)


#21

Urho3D::SharedPtr vs std:shared_ptr (VS 2017)

#include <Urho3D/Urho3DAll.h>

#include <iostream>
using namespace std;

int main()
{
    Context context;
    SharedPtr<Component> a, b;
    shared_ptr<Component> c, d;

    const long long count = 1000000LL;

    for (int k = 0; k < 10; k++)
    {
        HiresTimer timer;
        timer.Reset();
        for (long long i = 0; i < count; i++)
        {
            a = new Component(&context);;
            b = a;
        }
        long long elapsed = timer.GetUSec(false);
        cout << "Urho3D::SharedPtr " << elapsed << "\n";

        timer.Reset();
        for (long long i = 0; i < count; i++)
        {
            c = make_shared<Component>(&context);
            d = c;
        }
        elapsed = timer.GetUSec(false);
        cout << "std:shared_ptr " << elapsed << "\n";
    }

    // Prevent throw out pieces of code by compiler optimizer.
    clog << "Ignore it: " <<  b.Null() << d.use_count();

    // Prevent crash on exit when context deleted before objects.
    a.Detach();
    b.Detach();
}

Urho3D::SharedPtr 314000
std:shared_ptr 335000
Urho3D::SharedPtr 314000
std:shared_ptr 344000
Urho3D::SharedPtr 315000
std:shared_ptr 339000
Urho3D::SharedPtr 315000
std:shared_ptr 335000
Urho3D::SharedPtr 311000
std:shared_ptr 335000
Urho3D::SharedPtr 313000
std:shared_ptr 333000
Urho3D::SharedPtr 316000
std:shared_ptr 334000
Urho3D::SharedPtr 313000
std:shared_ptr 337000
Urho3D::SharedPtr 312000
std:shared_ptr 334000
Urho3D::SharedPtr 312000
std:shared_ptr 339000


#22

maybe it’s because Urho uses intrusive reference counting, with everything inheriting from RefCounted?
So we’re getting 2 ref counts with std::shared_ptr instead of one.

To fix the comparison you’ll need to create a class that inherits from RefCounted for Urho, and the same class without inheriting from RefCounted for std::shared_ptr.

This is how I corrected the code:

#include <Urho3D/Urho3DAll.h>

#include <iostream>
using namespace std;


struct TestUrho : RefCounted
{
	int x = 0;
};

struct Test
{
	int x = 0;
};


int main()
{
    SharedPtr<TestUrho> a, b;
    shared_ptr<Test> c, d;

    const long long count = 1000000LL;

    for (int k = 0; k < 10; k++)
    {
        HiresTimer timer;
        timer.Reset();
        for (long long i = 0; i < count; i++)
        {
            a = MakeShared<TestUrho>();
            b = a;
        }
        long long elapsed = timer.GetUSec(false);
        cout << "Urho3D::SharedPtr " << elapsed << "\n";

        timer.Reset();
        for (long long i = 0; i < count; i++)
        {
            c = make_shared<Test>();
            d = c;
        }
        elapsed = timer.GetUSec(false);
        cout << "std:shared_ptr " << elapsed << "\n";
    }

    // Prevent throw out pieces of code by compiler optimizer.
    clog << "Ignore it: " <<  b.Null() << d.use_count();
}

These are the results I got:

Urho3D::SharedPtr 140000
std:shared_ptr 79000
Urho3D::SharedPtr 127000
std:shared_ptr 77000
Urho3D::SharedPtr 142000
std:shared_ptr 78000
Urho3D::SharedPtr 141000
std:shared_ptr 78000
Urho3D::SharedPtr 126000
std:shared_ptr 79000
Urho3D::SharedPtr 126000
std:shared_ptr 78000
Urho3D::SharedPtr 137000
std:shared_ptr 78000
Urho3D::SharedPtr 141000
std:shared_ptr 81000
Urho3D::SharedPtr 130000
std:shared_ptr 78000
Urho3D::SharedPtr 143000
std:shared_ptr 80000

it seems that in this test std::shared_ptr is about 50% to 80% faster than Urho’s SharedPtr.

Note: with the old code SharedPtr was indeed faster.


#23

Refcounting preccessd only by SharedPtr class. In your example sizes of structs is different, so you have faster memory allocation for shared_ptr


#24

std::shared_ptr adds ref count externally, and Urho3D::SharedPtr can only be used with classes that inherit from Urho3D::RefCounted, so my benchmark is correct.


#25

Ah yes, u are right!

EDIT:

Urho3D::SharedPtr 133000
std:shared_ptr 78000
Urho3D::SharedPtr 141000
std:shared_ptr 94000
Urho3D::SharedPtr 131000
std:shared_ptr 77000
Urho3D::SharedPtr 141000
std:shared_ptr 94000
Urho3D::SharedPtr 125000
std:shared_ptr 93000
Urho3D::SharedPtr 125000
std:shared_ptr 94000
Urho3D::SharedPtr 125000
std:shared_ptr 95000
Urho3D::SharedPtr 141000
std:shared_ptr 78000
Urho3D::SharedPtr 141000
std:shared_ptr 78000
Urho3D::SharedPtr 141000
std:shared_ptr 78000

with your code


#26

With std pointers imposimple this thing:

void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
{
        ...
        // Use pointer to self to check for destruction after sending events
        WeakPtr<CrowdAgent> self(this);
        ...
        crowdManager_->SendEvent(E_CROWD_AGENT_REPOSITION, map);
        if (self.Expired())
            return;
        ...
}

#27

No it isn’t. The STL way is more verbose but can do the same thing using enable_shared_from_this and dynamic_pointer_cast.


#28

TBH, enable_shared_from_this isn’t the silver bullet. No way to do things in ctor, while intrusive ptrs have no problem there.


#29

If I understand correctly, enable_shared_from_this stores shared_ptr inside function and to allow deleting shared_ptr outside we need crazy constructions inside function to get weak_ptr from shared_ptr and delete shared_ptr


#30

Backwards. It only stores a weak_ptr internally, which means there’s no craziness involved.

Clarify? You can’t do a host of things in a constructor either way, virtual calls being the big one.


#31
  1. enable_shared_from_this requires to store weak_ptr in object, so… we get same overhead for object size, as when inheriting from Urho3D: RefCounted
  2. You can not recieve this weak_ptr directly. You can recieve only share_ptr, convert to weak_ptr and then delete shared_ptr to allow self-destruct object outside function and get correct num refs in weak_ptr inside function

actually Urho’s intrusive counter is different from boost https://github.com/boostorg/smart_ptr/blob/develop/include/boost/smart_ptr/intrusive_ref_counter.hpp

  1. Urho stores count of weak refs (what for?)
  2. Urho stores ref to RefCount instead storing of count in the object itself

EDIT: weak_from_this is part of c++17 http://en.cppreference.com/w/cpp/memory/enable_shared_from_this/weak_from_this


#32

I’ve been using STL-only in my local fork for quite a while, I haven’t had a single issue or encountered anything that could not be done that the Urho containers were doing.

The switch also made it a lot easier to work with 3rd-party libraries that are almost always going to be using STL as well as concurrency primitives that I had to have for multithreaded rendering.


#33

Cannot spawn weak/shared ptr from ctor.
And yes, it was a problem in my day job projects.
E.g. I was working with async server, and there’s no way to spawn tasks from ctor.


#34

it sounds a bit dangerous to use an object that wasn’t fully constructed yet, it’s kinda like as if it’s uninitialized.
You can always use some helper function that construct the object, then passes it to some other function.

BTW not all Urho objects need to use ref counting (ex: a subsystem with a known lifetime, usually until the program shuts down), and using std::shared_ptr gives the flexibility to choose not to.


#35

Meh, it’s sometimes dangerous. It wasn’t my case tho. Async task manager needs weak ptr just to automatically cancel tasks if owner is destroyed. And there’s no way to use it from ctor, despite it’s completely safe.