Search code examples
c++memoryboost-multi-index

Is it possible to make multi_index container use contiguous memory?


Here I have a simple multi_index container and I wonder if there is any way of forcing the multi_index to allocate the elements contiguously in memory. I thought that this would be possible if the main index is random_access.

However this simple example shows that unexpectedly the elements are not contiguous in memory. Is there a combination of boost::multi_index::indexed_by that would likely result in contiguous memory?

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>

int main(){
    typedef boost::multi_index_container<
        double,  // simply store doubles
        boost::multi_index::indexed_by<
            boost::multi_index::random_access<>
        >
    > random_access_container;

    random_access_container v; // fill container
    v.reserve(10); // also tried this
    v.push_back(1.);
    v.push_back(2.);
    v.push_back(3.);

    assert( v[0] == 1. ); // ok
    assert( *(&v[0] + 1) == v[1] ); // this fails, memory is not contiguous
}

NOTE 1: I want this for compatibility (so I can take advantage of multi_index container --with other access options--) but also use direct memory access (like it is possible with std::vector).

NOTE 2: I have just found this quote from the documentation, http://www.boost.org/doc/libs/1_61_0/libs/multi_index/doc/reference/rnd_indices.html#rnd_indices , so it is looking difficult.

Except where noted or if the corresponding interface does not exist, random access indices verify the same container requirements as std::vector plus the requirements for std::list specific list operations at [list.ops]. Some of the most important differences with respect to std::vector are:

  1. Random access indices do not provide memory contiguity, and hence do not have data member functions.

    ...


Solution

  • No, you don't have memory contiguity. The layout of a random-access index is akin to that of boost::container::stable_vector:

    random-access index memory layout

    An approximation to contiguous memory can be obtained if you store your elements (of type say T) in a std::vector<T> and then use a multi_index_container of std::ref<T>s. This complicates object lifetime management, of course.

    Edit: design rationales

    There are a number of reasons why memory contiguity is difficult/hard to include in the design of the library:

    • Iterator stability is provided by all indices, not merely random-access ones. It'd be impossible to keep this (with reasonable performance) if elements were stored contiguously in a chunk of memory.
    • Suppose we have somehow managed to get a random-access index induce memory contiguity with respect to element storage. What would happen if we have two random-access indices? Seems like the first index of the container ought to have a special status in terms of dictating the layout of the whole container for this to hold water.
    • Non-random-access indices are node-based by necessity. This means that each value is stored in a bigger struct with room for additional info (rb-tree pointers etc.) If elements were stored contiguoulsy then either a) it'd be the nodes that'd be stored contiguously, not the values themselves, which seems pretty useless (think what data() would return), or b) the nodes would have to be detached from the values, so that rather than embedding the value in the node we'd have nodes with pointers to the contiguously-stored values, which is a waste of space and does not look like a resonable default decision.