Jump to content
  • Advertisement
Sign in to follow this  
muse1987

"Inline" reference counting?

This topic is 3667 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi. I am currently using slightly modified implementation of reference counting which I think as an improvement. I'd like to know what others think about it, as so many times I miss something important and what I think as improvement is actually a bad idea. OK here goes my code;
class RefCountObject
{
private:
    // Reference count
    unsigned int value;

public:
    virtual void dispose()
        { delete this; }

    inline void addref() { ++ this->value; }

    inline void release()
    {
       if( 0 == -- this->value )
           this->dispose();
    }

    RefCountObject() : value(1) {}
    virtual ~RefCountObject() {}
};

struct RefCount : virtual RefCountObject {};


// usage...
class Foo : public RefCount
{
    // ...
};

Foo* p = new Foo;
p->addref();
p->release();
p->release();

What I think as improvement is that addref() and release() functions will be inlined thus speeding up the code. But I haven't seen such implementation in others' code and now wonder why they don't do it if it's actually good idea? Did I miss something? Thanks in advance.

Share this post


Link to post
Share on other sites
Advertisement
Oh dear.

I think I'll respond before people with harsher words do, but you're a bit misguided. The compiler is free to ignore your demands to inline code, and is also (and encouraged to be) free to inline code you don't specify. That means for titchy little functions like the ones in Boost.SharedPtr, it'll almost certainly be inlined (consider: in C++, any function defined inside a class definition has inline applied to it implicitly). The reason people use smart pointers over doing "addref/release" manually is because doing it manually adds nothing. You are still required to manually track "usages". Using smart pointers alleviate the overhead of tracking "usages", making you only responsible for defining "ownership" - which is really easy. Maybe you haven't seen it before, having said so yourself?


// in some scope somewhere
{
boost::shared_ptr<int> my_int(new int(6));

// use my_int as a regular pointer ...
}
// end of scope, pow! memory deallocated.



There's no reason not to use smart pointers - they are customizable enough to be used for COM objects, D3D resources, etc. You only need to tweak things a little. Plus, they're well, well, well tested - they're in HUGE PILES of production software in the world today. Considering how small the shared-pointers are, it's safe to say that all bugs in them have been ironed out. They're more flexible, more portable, and better documented.

Basically, your reinvention of the wheel offers nothing over the other (industry standard, tested, debugged, flawless) wheel.

Share this post


Link to post
Share on other sites
I'm not sure if you know this, but just by defining it in the class makes it implicitly inline. I would say it is probably a good idea since it is such a small operation and won't bloat the code too much. The compiler will ignore it if it doesn't think that it is a good idea as well, so I would say go for it.

I would recommend using tr1::shared_ptr or boost::shared_ptr, unless you are really into having your own reference counter.

Share this post


Link to post
Share on other sites
Quote:
Original post by muse1987

What I think as improvement is that addref() and release() functions will be inlined thus speeding up the code. But I haven't seen such implementation in others' code and now wonder why they don't do it if it's actually good idea?


It depends which compiler they're targetting. Boost being mostly templated (and hence header-centric) doesn't and lets compiler inline as it chooses.

For some platforms, smaller code is actually faster, so inlining isn't strictly desired, and is performed after profiling.

Despite convenience of reference counting, passing references all over the place frequently shouldn't be common. As such, the number of references and cost of count is usually minimal.

boost::shared_ptr is exception since it performs thread-safe reference counting, adding non-trivial overhead for simple increment. Still, it's not noticable for common usage. Frequent creations of shared_ptr or passing it in tight loops however can be a problem.

If reference counting is automatic one can still pass smart pointers by reference and avoid the count change unless really needed.

So typically the act of counting reference itself shouldn't be a problem, especially not with explicit reference counting (vs. implicit via assingment/copy-constructor).

Share this post


Link to post
Share on other sites
@_goat : Surely I have implemented a smart pointer. I just ommitted it as I thought that's not important here.

@Treb : Yes I know they're implicitly inlined. I just specify it because I prefer long code [lol]

@_goat and Treb : boost::shared_ptr (IMHO) has disadvantage in performance. I'm not saying it's badly implemented, rather that comes from its versatility. As _goat mentioned boost::shared_ptr can even hold pointer to int, while my smart pointer can hold only classes derived from RefCount. In order to allow that boost::shared_ptr allocates an object internally, called boost::detail::sp_counted_base. So what happens? Each object stored in shared_ptr will cause another object allocated on the heap(only when two or more shared_ptr point to same object but I'm sure that's 99% case). I see the sizeof(sp_counted_base) == 8. When it allocated on the heap there's memory overhead. I think it actually consumes 32 bytes in Win32. Also caring that heap operations are relatively costly I minimize dependency to dynamic memory in my code. That's why I wrote my own.
Sorry I didn't mean to start a flame war. I just wanted to say what I think. You're welcome to correct me.

Share this post


Link to post
Share on other sites
@Antheus :
Quote:
boost::shared_ptr is exception since it performs thread-safe reference counting

Oh I forgot it. Thanks for telling me that. I just corrected my code to be thread-safe.
Quote:
If reference counting is automatic one can still pass smart pointers by reference and avoid the count change unless really needed.

Well I think that makes sense.

Share this post


Link to post
Share on other sites
Quote:
Original post by muse1987
@_goat and Treb : boost::shared_ptr (IMHO) has disadvantage in performance. I'm not saying it's badly implemented, rather that comes from its versatility. As _goat mentioned boost::shared_ptr can even hold pointer to int, while my smart pointer can hold only classes derived from RefCount. In order to allow that boost::shared_ptr allocates an object internally, called boost::detail::sp_counted_base. So what happens? Each object stored in shared_ptr will cause another object allocated on the heap(only when two or more shared_ptr point to same object but I'm sure that's 99% case). I see the sizeof(sp_counted_base) == 8. When it allocated on the heap there's memory overhead. I think it actually consumes 32 bytes in Win32. Also caring that heap operations are relatively costly I minimize dependency to dynamic memory in my code. That's why I wrote my own.
Sorry I didn't mean to start a flame war. I just wanted to say what I think. You're welcome to correct me.


You know, requiring a class to inherit from your base class (if it didn't already inherit from something else) also adds (at least) 8 bytes of overhead (4 for the counter and 4 for the vtable pointer)... Sure it's allocated in one chunk, rather than 2, but it's still there.

And the overhead of your method exists no matter how many references you have. You'd be surprised - most shared_ptr's don't have more than one reference. Of course, that depends on usage, so may not be true in all cases.

In any case, this seems like a case of premature optimization, to me.

On another topic, most compilers these days have a feature called "whole program optimization". Basically, the linker is able to do various optimizations in addition to the compiler. So even if the methods were not defined in the header, they could still be inlined.

Share this post


Link to post
Share on other sites
Is your share_ptr alike (refcount) class are exception safe and exception neutral?

The standard has spent lots of time developing & testing with these. How about yours?

Share this post


Link to post
Share on other sites
Quote:
boost::shared_ptr (IMHO) has disadvantage in performance. I'm not saying it's badly implemented, rather that comes from its versatility. As _goat mentioned boost::shared_ptr can even hold pointer to int, while my smart pointer can hold only classes derived from RefCount. In order to allow that boost::shared_ptr allocates an object internally, called boost::detail::sp_counted_base. So what happens? Each object stored in shared_ptr will cause another object allocated on the heap(only when two or more shared_ptr point to same object but I'm sure that's 99% case). I see the sizeof(sp_counted_base) == 8. When it allocated on the heap there's memory overhead. I think it actually consumes 32 bytes in Win32. Also caring that heap operations are relatively costly I minimize dependency to dynamic memory in my code. That's why I wrote my own.
If you're really worried about this, take a look at boost::intrusive_ptr. It has many of the advantages of shared_ptr (widely used, thoroughly tested), but allows you to implement your own reference counting mechanism.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!