Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Managed Heap Memory And Managed External Buffer

Managed External Buffer: Constructing all Boost.Interprocess objects in a user provided buffer
Managed Heap Memory: Boost.Interprocess machinery in heap memory
Differences between managed memory segments
Example: Serializing a database through the message queue

Boost.Interprocess offers managed shared memory between processes using managed_shared_memory or managed_mapped_file. Two processes just map the same the memory mappable resource and read from and write to that object.

Many times, we don't want to use that shared memory approach and we prefer to send serialized data through network, local socket or message queues. Serialization can be done through Boost.Serialization or similar library. However, if two processes share the same ABI (application binary interface), we could use the same object and container construction capabilities of managed_shared_memory or managed_heap_memory to build all the information in a single buffer that will be sent, for example, though message queues. The receiver would just copy the data to a local buffer, and it could read or modify it directly without deserializing the data . This approach can be much more efficient that a complex serialization mechanism.

Applications for Boost.Interprocess services using non-shared memory buffers:

To help with this management, Boost.Interprocess provides two useful classes, basic_managed_heap_memory and basic_managed_external_buffer:

Sometimes, the user wants to create simple objects, STL compatible containers, STL compatible strings and more, all in a single buffer. This buffer could be a big static buffer, a memory-mapped auxiliary device or any other user buffer.

This would allow an easy serialization and we-ll just need to copy the buffer to duplicate all the objects created in the original buffer, including complex objects like maps, lists.... Boost.Interprocess offers managed memory segment classes to handle user provided buffers that allow the same functionality as shared memory classes:

//Named object creation managed memory segment
//All objects are constructed in a user provided buffer
template <
            class CharType,
            class MemoryAlgorithm,
            template<class IndexConfig> class IndexType
         >
class basic_managed_external_buffer;

//Named object creation managed memory segment
//All objects are constructed in a user provided buffer
//   Names are c-strings,
//   Default memory management algorithm
//    (rbtree_best_fit with no mutexes and relative pointers)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_external_buffer <
   char,
   rbtree_best_fit<null_mutex_family, offset_ptr<void> >,
   flat_map_index
   >  managed_external_buffer;

//Named object creation managed memory segment
//All objects are constructed in a user provided buffer
//   Names are wide-strings,
//   Default memory management algorithm
//    (rbtree_best_fit with no mutexes and relative pointers)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_external_buffer<
   wchar_t,
   rbtree_best_fit<null_mutex_family, offset_ptr<void> >,
   flat_map_index
   >  wmanaged_external_buffer;

To use a managed external buffer, you must include the following header:

#include <boost/interprocess/managed_external_buffer.hpp>

Let's see an example of the use of managed_external_buffer:

#include <boost/interprocess/managed_external_buffer.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/container/list.hpp>
#include <cstring>
#include <boost/aligned_storage.hpp>

int main()
{
   using namespace boost::interprocess;

   //Create the static memory who will store all objects
   const int memsize = 65536;

   static boost::aligned_storage<memsize>::type static_buffer;

   //This managed memory will construct objects associated with
   //a wide string in the static buffer
   wmanaged_external_buffer objects_in_static_memory
      (create_only, &static_buffer, memsize);

   //We optimize resources to create 100 named objects in the static buffer
   objects_in_static_memory.reserve_named_objects(100);

   //Alias an integer node allocator type
   //This allocator will allocate memory inside the static buffer
   typedef allocator<int, wmanaged_external_buffer::segment_manager>
      allocator_t;

   //Alias a STL compatible list to be constructed in the static buffer
   typedef boost::container::list<int, allocator_t>    MyBufferList;

   //The list must be initialized with the allocator
   //All objects created with objects_in_static_memory will
   //be stored in the static_buffer!
   MyBufferList *list = objects_in_static_memory.construct<MyBufferList>(L"MyList")
                           (objects_in_static_memory.get_segment_manager());
   //Since the allocation algorithm from wmanaged_external_buffer uses relative
   //pointers and all the pointers constructed int the static memory point
   //to objects in the same segment,  we can create another static buffer
   //from the first one and duplicate all the data.
   static boost::aligned_storage<memsize>::type static_buffer2;
   std::memcpy(&static_buffer2, &static_buffer, memsize);

   //Now open the duplicated managed memory passing the memory as argument
   wmanaged_external_buffer objects_in_static_memory2
      (open_only, &static_buffer2, memsize);

   //Check that "MyList" has been duplicated in the second buffer
   if(!objects_in_static_memory2.find<MyBufferList>(L"MyList").first)
      return 1;

   //Destroy the lists from the static buffers
   objects_in_static_memory.destroy<MyBufferList>(L"MyList");
   objects_in_static_memory2.destroy<MyBufferList>(L"MyList");
   return 0;
}

Boost.Interprocess STL compatible allocators can also be used to place STL compatible containers in the user segment.

basic_managed_external_buffer can be also useful to build small databases for embedded systems limiting the size of the used memory to a predefined memory chunk, instead of letting the database fragment the heap memory.

Note: The external memory supplied by the user shall be aligned to the maximum value between alignof(max_align_t) and the alignment of the strictest over-aligned type to be built inside that memory.

The use of heap memory (new/delete) to obtain a buffer where the user wants to store all his data is very common, so Boost.Interprocess provides some specialized classes that work exclusively with heap memory.

These are the classes:

//Named object creation managed memory segment
//All objects are constructed in a single buffer allocated via new[]
template <
            class CharType,
            class MemoryAlgorithm,
            template<class IndexConfig> class IndexType
         >
class basic_managed_heap_memory;

//Named object creation managed memory segment
//All objects are constructed in a single buffer allocated via new[]
//   Names are c-strings,
//   Default memory management algorithm
//    (rbtree_best_fit with no mutexes and relative pointers)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_heap_memory <
   char,
   rbtree_best_fit<null_mutex_family>,
   flat_map_index
   >  managed_heap_memory;

//Named object creation managed memory segment
//All objects are constructed in a single buffer allocated via new[]
//   Names are wide-strings,
//   Default memory management algorithm
//    (rbtree_best_fit with no mutexes and relative pointers)
//   Name-object mappings are stored in the default index type (flat_map)
typedef basic_managed_heap_memory<
   wchar_t,
   rbtree_best_fit<null_mutex_family>,
   flat_map_index
   >  wmanaged_heap_memory;

To use a managed heap memory, you must include the following header:

#include <boost/interprocess/managed_heap_memory.hpp>

The use is exactly the same as basic_managed_external_buffer, except that memory is created by the managed memory segment itself using dynamic (new/delete) memory.

basic_managed_heap_memory also offers a grow(std::size_t extra_bytes) function that tries to resize internal heap memory so that we have room for more objects. But be careful, if memory is reallocated, the old buffer will be copied into the new one so all the objects will be binary-copied to the new buffer. To be able to use this function, all pointers constructed in the heap buffer that point to objects in the heap buffer must be relative pointers (for example offset_ptr). Otherwise, the result is undefined. Here is an example:

#include <boost/container/list.hpp>
#include <boost/interprocess/managed_heap_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <cstddef>
#include <cassert>

using namespace boost::interprocess;
typedef boost::container::list<int, allocator<int, managed_heap_memory::segment_manager> >
   MyList;

int main ()
{
   //We will create a buffer of 1000 bytes to store a list
   managed_heap_memory heap_memory(1000);

   MyList * mylist = heap_memory.construct<MyList>("MyList")
                        (heap_memory.get_segment_manager());

   //Obtain handle, that identifies the list in the buffer
   managed_heap_memory::handle_t list_handle = heap_memory.get_handle_from_address(mylist);

   //Fill list until there is no more memory in the buffer
   try {
      while(1) {
         mylist->insert(mylist->begin(), 0);
      }
   }
   catch(const bad_alloc &){
      //memory is full
   }
   //Let's obtain the size of the list
   MyList::size_type old_size = mylist->size();

   //To make the list bigger, let's increase the heap buffer
   //in 1000 bytes more.
   heap_memory.grow(1000);

   //If memory has been reallocated, the old pointer is invalid, so
   //use previously obtained handle to find the new pointer.
   mylist = static_cast<MyList *>
               (heap_memory.get_address_from_handle(list_handle));

   //Fill list until there is no more memory in the buffer
   try {
      while(1) {
         mylist->insert(mylist->begin(), 0);
      }
   }
   catch(const bad_alloc &){
      //memory is full
   }

   //Let's obtain the new size of the list
   MyList::size_type new_size = mylist->size();

   assert(new_size > old_size);

   //Destroy list
   heap_memory.destroy_ptr(mylist);

   return 0;
}

Note: Heap memory is not guaranteed to be compatible with over-aligned types so it is not supported to use memory allocation algorithms or construct objects that need over-aligned memory:

// 64 byte aligned memory might not be supported
typedef basic_managed_heap_memory<
    char,
    rbtree_best_fit<mutex_family, offset_ptr<void, long, unsigned long, 64>, 64>,
    iset_index
> managed_heap_with_overalignment_t;

All managed memory segments have similar capabilities (memory allocation inside the memory segment, named object construction...), but there are some remarkable differences between managed_shared_memory, managed_mapped_file and managed_heap_memory, managed_external_file.

  • Default specializations of managed shared memory and mapped file use process-shared mutexes. Heap memory and external buffer have no internal synchronization by default. The cause is that the first two are thought to be shared between processes (although memory mapped files could be used just to obtain a persistent object data-base for a process) whereas the last two are thought to be used inside one process to construct a serialized named object data-base that can be sent though serial interprocess communications (like message queues, localhost network...).
  • The first two create a system-global object (a shared memory object or a file) shared by several processes, whereas the last two are objects that don't create system-wide resources.

To see the utility of managed heap memory and managed external buffer classes, the following example shows how a message queue can be used to serialize a whole database constructed in a memory buffer using Boost.Interprocess, send the database through a message queue and duplicated in another buffer:

//This test creates a in memory data-base using Interprocess machinery and
//serializes it through a message queue. Then rebuilds the data-base in
//another buffer and checks it against the original data-base
bool test_serialize_db()
{
   //Typedef data to create a Interprocess map
   typedef std::pair<const std::size_t, std::size_t> MyPair;
   typedef std::less<std::size_t>   MyLess;
   typedef node_allocator<MyPair, managed_external_buffer::segment_manager>
      node_allocator_t;
   typedef boost::container::map<std::size_t,
               std::size_t,
               std::less<std::size_t>,
               node_allocator_t>
               MyMap;

   //Some constants
   const std::size_t BufferSize  = 65536;
   const std::size_t MaxMsgSize  = 100;

   //Allocate a memory buffer to hold the destiny database using vector<char>
   std::vector<char> buffer_destiny(BufferSize, 0);

   message_queue::remove("MyName");
   {
      //Create the message-queues
      message_queue mq1(create_only, "MyName", 1, MaxMsgSize);

      //Open previously created message-queue simulating other process
      message_queue mq2(open_only, "MyName");

      //A managed heap memory to create the origin database
      managed_heap_memory db_origin(buffer_destiny.size());

      //Construct the map in the first buffer
      MyMap *map1 = db_origin.construct<MyMap>("MyMap")
                                       (MyLess(),
                                       db_origin.get_segment_manager());
      if(!map1)
         return false;

      //Fill map1 until is full
      try {
         std::size_t i = 0;
         while(1){
            (*map1)[i] = i;
            ++i;
         }
      }
      catch(boost::interprocess::bad_alloc &){}

      //Data control data sending through the message queue
      std::size_t sent = 0;
      message_queue::size_type recvd = 0;
      message_queue::size_type total_recvd = 0;
      unsigned int priority;

      //Send whole first buffer through the mq1, read it
      //through mq2 to the second buffer
      while(1){
         //Send a fragment of buffer1 through mq1
       std::size_t bytes_to_send = MaxMsgSize < (db_origin.get_size() - sent) ?
                                       MaxMsgSize : (db_origin.get_size() - sent);
         mq1.send( &static_cast<char*>(db_origin.get_address())[sent]
               , bytes_to_send
               , 0);
         sent += bytes_to_send;
         //Receive the fragment through mq2 to buffer_destiny
       mq2.receive( &buffer_destiny[total_recvd]
                , BufferSize - recvd
                  , recvd
                  , priority);
         total_recvd += recvd;

         //Check if we have received all the buffer
         if(total_recvd == BufferSize){
            break;
         }
      }

      //The buffer will contain a copy of the original database
      //so let's interpret the buffer with managed_external_buffer
      managed_external_buffer db_destiny(open_only, &buffer_destiny[0], BufferSize);

      //Let's find the map
      std::pair<MyMap *, managed_external_buffer::size_type> ret = db_destiny.find<MyMap>("MyMap");
      MyMap *map2 = ret.first;

      //Check if we have found it
      if(!map2){
         return false;
      }

      //Check if it is a single variable (not an array)
      if(ret.second != 1){
         return false;
      }

      //Now let's compare size
      if(map1->size() != map2->size()){
         return false;
      }

      //Now let's compare all db values
     MyMap::size_type num_elements = map1->size();
     for(std::size_t i = 0; i < num_elements; ++i){
         if((*map1)[i] != (*map2)[i]){
            return false;
         }
      }

      //Destroy maps from db-s
      db_origin.destroy_ptr(map1);
      db_destiny.destroy_ptr(map2);
   }
   message_queue::remove("MyName");
   return true;
}

PrevUpHomeNext