Garbage Collection in The Age of Smart Pointers

January 12, 2012

8 comments

A few days ago I was asked on Twitter whether research into garbage collection is paying off, considering the super-smart-pointers introduced into C++ and other language and library tricks. Let’s take a look at some of the smart pointer facilities introduced in C++11, and then tackle them from a garbage collection perspective.

C++11 features three standard smart pointer classes:

  • unique_ptr<T> wraps a pointer to a value and guarantees that there is only one pointer at a time to that value. You cannot copy unique_ptr<T> instances around (only move them), and when the unique_ptr<T> instance is destroyed, the underlying pointer is deleted.
  • shared_ptr<T> wraps a pointer to a value and a reference count, allowing multiple locations in the program to point to the same value at the same time. You can copy shared_ptr<T> instances around, and when the last shared_ptr<T> instance with a given pointer is destroyed, the underlying pointer is deleted.
  • weak_ptr<T> wraps a pointer to a value and can be converted to a shared_ptr<T>, temporarily (using the lock() method). However, while you have only a weak_ptr<T> instance, the underlying pointer can be deleted (because there are no “strong” pointers to it), and then your attempt to convert it to a shared_ptr<T> will fail gracefully. (This is very similar to the implementation of short weak references in garbage collected languages, such as .NET’s WeakReference class.)

From a garbage collection perspective, unique_ptrs are not that interesting because they wrap access to a uniquely owned pointer, which is not available to anyone else. Shared_ptrs and weak_ptrs, however, are more interesting, because they provide actual semantics for shared ownership of resources and their eventual reclamation. Moreover, the fundamental problem of reference counting GC, namely that of cycles (e.g. see Python GC treatment of this issue), is addressed by providing a fairly convenient weak pointer concept.

What are the benefits of using automatic garbage collection when you have shared pointers and weak pointers, and don’t have to call “delete” in your C++ programs anymore?

Reference counting is costly. Every copy of a reference (shared_ptr, in C++’s case), involves updating a reference count, and that update requires multiprocessor synchronization. Even worse, if an object is shared across multiple threads, the reference count updates may invalidate cache lines on multiple processors. In the C++ shared_ptr, only the control block will be invalidated, but in “legacy” reference counting environments, where the reference count is embedded in the object, the object itself may be invalidated, which has an even higher performance cost.

Using weak_ptr is counter-intuitive and hard. As a programmer, I don’t want to be forced to analyze and break every reference cycle in my application. There are the obvious textbook examples with managers referencing their direct reports referencing back their managers, which can be broken with weak_ptrs; however, imagine a cycle that is not formed deterministically and that contains dozens of objects – even figuring out that you have a cycle is an impossible task.

Using smart pointers requires extraordinary consistency. C++ developers might be able to go on a “delete-elimination” crusade and convert their entire application to use smart pointers. I just don’t see this library feature ever being as convenient as standard C# references.

Reclaiming many objects at once might be more efficient than reclaiming each individual object. You may have heard horror stories about multi-second delays introduced by garbage collections (missiles diverted from their path, anyone?), but most garbage collections have sub-millisecond times, and most applications, even on fully-loaded servers, don’t suffer by default from garbage collection latencies. In fact, when you think about it, garbage collection costs (in the small object heap) are roughly linear in the number of live objects, so if most of your heap is garbage, collecting it is a fairly rapid task. On the other hand, if you reclaimed each object individually, you’d pay the same price for each piece of that garbage, instead of reclaiming it in one fell swoop.

Now, if you consider some of the “futuristic” advances in garbage collection, you might be even more inclined to prefer it over smart pointers. Take a look at Azul’s Zing pauseless garbage collection, which runs concurrently with the application and introduces only very rare and short pause times, even for huge (~100GB) heaps. If this is the future of garbage collection, we won’t need smart pointers.


 I am posting short updates and links on Twitter as well as on this blog. You can follow me: @goldshtn

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

8 comments

  1. SamJanuary 12, 2012 ב 10:42 PM

    What about the defragmentation features of managed memory? I find the efficiency of having giant empty space pool in the heap far more compelling than simple garbage collection. Especially considering how complicated memory allocation algorithms can get.

    I prefer to think of garbage collection as a side-effect of memory defragmentation rather than the reason for using managed memory.

    Reply
  2. JonJanuary 13, 2012 ב 12:53 PM

    Reference counting is costly? Doesn’t garbage collection use reference counting?

    Reply
  3. GuestJanuary 14, 2012 ב 5:42 AM

    agreed, plus, reference counting doesn’t do compacting, so it leads to fragmentation.

    Reply
  4. POKE53280,0January 15, 2012 ב 2:00 AM

    The problem is that .NET GC can be OK for memory resources, but doesn’t have a clue on non-memory resources (like textures, sockets, files…). Instead, C++ destructors (and smart pointers) can be used to handle every kind of resource uniformly, releasing it as soon as possible.

    Reply
  5. Sasha GoldshteinJanuary 20, 2012 ב 3:21 PM

    @POKE53280,0: You can always manage non-memory resources yourself, the same way you do with destructors in C++ (after all, you’re responsible for calling the destructor). And there’s the “using” statement. But I agree, there’s room for improvement here.

    @Jon: No, most garbage collection implementations do not use reference counting.

    Reply
  6. CatalinJanuary 29, 2012 ב 12:23 AM

    You missed the core aspect of the things: what about DETERMINISTIC vs NON-DETERMINISTIC facet of the problem. This is not a minor issue, fast system programming can’t rely on non-deterministic destruction, so I don’t agree with the conclusion.

    Reply
  7. Karan KadamJanuary 29, 2012 ב 7:35 PM

    Excellent article. Good insight into GC vs smart pointers. Thanks!

    Reply
  8. Fulvio EspositoJanuary 30, 2012 ב 10:22 PM

    Well, C++11 offers a minimal support for garbage collection, but the point is that C++ memory model is always deterministic.

    When you use automatic storage, dtors are granted to be excuted upon block leaving (whatever is the reason, so don’t need to try/catch/finally).

    With unique_ptr we have the same behaviour of automatic objects for objects allocated on the heap

    With shared_prt/weak_ptr dtors are granted to be executed when last owner is destoyed.

    All of this means that we manage all the resources, not just the memory, the same way and there’s always a guarantee about resource deallocation time (that’s a very valuable and desiderable property).

    If fragmentation is a problem, C++ let you override standard allocatino mechanism. But fragmentation is a problem just in very limited contexts. If allocation time is a problem, no-one forbid to provide a memory allocation scheme similar to what a garbage collection system do, preallocate a large block and just returns pointer to internal offset of the block on memory demand).

    In my opinion, with GC we trade all resource management for just automatic memory management.

    in c++

    {
    fstream f( “a.out” );
    f << “hello”;
    }
    {
    fstream f( “a.out” );
    f << “hello”;
    }

    the equivalent correct java version is

    {
    FileStream fs = new FileStream( … );
    try
    {
    fs.write( “hello” );
    }
    finally
    {
    if( fs != null )
    fs.close();
    }
    try
    {
    fs.write( “hello” );
    }
    finally
    {
    if( fs != null )
    fs.close();
    }

    }

    it’s really an improvement in productivity?

    Just my 2 cents

    Reply