Clicky

A use case for intrusive pointers

Why use intrusive pointers?

In the docs for boost intrusive_ptr it states:

The main reasons to use intrusive_ptr are:

  • Some existing frameworks or OSes provide objects with embedded reference counts;
  • The memory footprint of intrusive_ptr is the same as the corresponding raw pointer;
  • intrusive_ptr<T> can be constructed from an arbitrary raw pointer of type T *.

Elsewhere on the webs people talk about also using intrusive pointers for performance reasons, claiming that intrusive pointers are faster than shared pointers.

All of these things are fine reasons to consider using intrusive_ptr, however now I will present yet another reason to use them.

Intrusive pointers for buffer pool management

In real time systems allocating memory at run time is frowned upon, a typical real time application will allocate enough memory at system init time for the worst case scenario and continuously re-use this memory. Often times memory is allocated in blocks and stored in a queue for quick access. This queue of memory buffers (aka a buffer pool) is then used to quickly get a memory block at run time (i.e. by real time tasks), and when the memory is no longer in use it is returned to the pool.

There are many ways to do this, but it strikes me as a perfect use case for intrusive pointers. The idea is that at system init time a pool of intrusive_ptrs to memory blocks is created, and then when the intrusive pointer reference count hits zero, normally the memory would be deallocated, but in this case it would just be added back to the pool for reuse.

Why use intrusive pointers for buffer pool management?

In the past I have used many methods for buffer pool management including:

  • Manually get and release buffers (i.e. no reference counting)
    • This is error prone, as buffers can leak, or be released multiple times.
  • Use reference counting and a special low priority thread to monitor when buffers can be returned to the pool.
    • This method requires the additional overhead of an extra thread, the thread keeps a list of all buffers that are in use, and loops over the list to look for buffers with a use_count of 1, then it moves such buffers back to the pool for reuse. There is a lot of unnecessary overhead here, as well as the possibility for thread starvation which would lead to running out of buffers.
  • Finally this intrusive_ptr method.
    • This method is much more in tune with the RAII idiom, does not have much extra overhead, and it is very nice to know that as soon as a buffer is no longer in use it is immediately put back in the pool.

Implementation details

An implementation of such a buffer pool can be found at QueuePtr this module contains the following things which are all used to implement a thread safe intrusive pointer buffer pool:

  • A ThreadSafeQueue, used as the foundation for the buffer pool.
  • A ThreadSafePool, a pool is a Queue that is populated at system init time.
  • An IntrusivePtrBase class which actually implements the reference counting.
  • A RefCntBuffer, and a RefCntBufferPool which provide the public interface for the whole thing.

To create a pool, simply allocate a RefCntBufferPool:

boost::shared_ptr<RefCntBufferPool> _pool(new RefCntBufferPool(5, 1024));

Where the first parameter is the number of buffers to add to the pool, and the second parameter is the size of each buffer.

Then to get a buffer, just dequeue from the pool into a boost::intrusive_ptr<RefCntBuffer> as follows:

boost::intrusive_ptr<RefCntBuffer> x;
if (_pool->dequeue(x) == true)
{
    // Got it
}
else
{
    // No buffers in the pool!
}

Once a buffer has been dequeued it can be used anyway memory is normally used, the current implementation exposes a boost::asio::mutable_buffer _buffer member from the RefCntBuffer, and the starting offset, length, and of course data of the mutable_buffer can be modified any way you like. The starting offset and length are restored to their original values when the buffer is dequeued.

The following is a full example of how the whole system works, and how the buffer can be abused:

#include <iostream>
#include "RefCntBufferPool.h"

int main()
{ 
    boost::shared_ptr<RefCntBufferPool> _pool(new RefCntBufferPool(5, 1024));
	for (int i = 0; i < 1000; i++)
	{
		boost::intrusive_ptr<RefCntBuffer> x;
		_pool->dequeue(x);
		x->_buffer = boost::asio::buffer(x->_buffer + 10, 100);
		boost::asio::buffer_copy(x->_buffer, boost::asio::buffer("hello"));
		x.reset();
	}
    std::cout << "done" << std::endl;
}

Further uses of the intrusive pointer buffer pool be seen in the ComBomb source code.

Posted in Programming

Leave a Reply

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

*