This section will show performance tests comparing some operations on boost::intrusive::list and
std::list:

Insertions using push_back
and container destruction will show the overhead associated with memory
allocation/deallocation.

The reverse member function
will show the advantages of the compact memory representation that can
be achieved with intrusive containers.

The sort and writeaccess
tests will show the advantage of intrusive containers minimizing memory
accesses compared to containers of pointers.

Given an object of type T,
boost::intrusive::list<T>
can replace std::list<T> to
avoid memory allocation overhead, or it can replace std::list<T*> when
the user wants containers with polymorphic values or wants to share values
between several containers. Because of this versatility, the performance tests
will be executed for 6 different list types:

3 intrusive lists holding a class named itest_class,
each one with a different linking policy (normal_link,
safe_link, auto_unlink). The itest_class
objects will be tightly packed in a std::vector<itest_class> object.

std::list<test_class>,
where test_class is exactly
the same as itest_class,
but without the intrusive hook.

Both test_class and itest_class are templatized classes that
can be configured with a boolean to increase the size of the object. This way,
the tests can be executed with small and big objects. Here is the first part
of the testing code, which shows the definitions of test_class
and itest_class classes, and
some other utilities:

The first test will measure the benefits we can obtain with intrusive containers
avoiding memory allocations and deallocations. All the objects to be inserted
in intrusive containers are allocated in a single allocation call, whereas
std::list will need to allocate memory for each
object and deallocate it for every erasure (or container destruction).

Let's compare the code to be executed for each container type for different
insertion tests:

std::vector<typenameilist::value_type>objects(NumElements);ilistl;for(inti=0;i<NumElements;++i)l.push_back(objects[i]);//Elements are unlinked in ilist's destructor//Elements are destroyed in vector's destructor

For intrusive containers, all the values are created in a vector and after
that inserted in the intrusive list.

std::vector<typenamestdlist::value_type>objects(NumElements);stdptrlistl;for(inti=0;i<NumElements;++i)l.push_back(&objects[i]);//Pointers to elements unlinked and destroyed in stdptrlist's destructor//Elements destroyed in vector's destructor

For a standard compact pointer list, elements are created in a vector and
pushed back in the pointer list using push_back().

stdlistobjects;stdptrlistl;for(inti=0;i<NumElements;++i){objects.push_back(typenamestdlist::value_type(i));l.push_back(&objects.back());}//Pointers to elements unlinked and destroyed in stdptrlist's destructor//Elements unlinked and destroyed in stdlist's destructor

For a disperse pointer list, elements are created in
a list and pushed back in the pointer list using push_back().

These are the times in microseconds for each case, and the normalized time:

The results are logical: intrusive lists just need one allocation. The destruction
time of the normal_link intrusive
container is trivial (complexity: O(1)),
whereas safe_link and auto_unlink intrusive containers need to
put the hooks of erased values in the default state (complexity: O(NumElements)). That's why normal_link
intrusive list shines in this test.

Non-intrusive containers need to make many more allocations and that's why
they lag behind. The dispersepointerlist
needs to make NumElements*2 allocations,
so the result is not surprising.

The Linux test shows that standard containers perform very well against intrusive
containers with big objects. Nearly the same GCC version in MinGW performs
worse, so maybe a good memory allocator is the reason for these excellent
results.

The next test measures the time needed to complete calls to the member function
reverse().
Values (test_class and itest_class) and lists are created as explained
in the previous section.

Note that for pointer lists, reversedoes not need to access test_class
values stored in another list or vector, since this function just
needs to adjust internal pointers, so in theory all tested lists need to
perform the same operations.

For small objects the results show that the compact storage of values in
intrusive containers improve locality and reversing is faster than with standard
containers, whose values might be dispersed in memory because each value
is independently allocated. Note that the dispersed pointer list (a list
of pointers to values allocated in another list) suffers more because nodes
of the pointer list might be more dispersed, since allocations from both
lists are interleaved in the code:

For big objects the compact pointer list wins because the reversal test doesn't
need access to values stored in another container. Since all the allocations
for nodes of this pointer list are likely to be close (since there is no
other allocation in the process until the pointer list is created) locality
is better than with intrusive containers. The dispersed pointer list, as
with small values, has poor locality.

The next test measures the time needed to complete calls to the member function
sort(Predpred). Values (test_class
and itest_class) and lists
are created as explained in the first section. The values will be sorted
in ascending and descending order each iteration. For example, if l
is a list:

Note that for pointer lists, sort
will take a function object that will access test_class values stored in another list
or vector, so pointer lists will suffer an extra indirection:
they will need to access the test_class
values stored in another container to compare two elements.

The results show that intrusive containers are faster than standard containers.
We can see that the pointer list holding pointers to values stored in a vector
is quite fast, so the extra indirection that is needed to access the value
is minimized because all the values are tightly stored, improving caching.
The disperse list, on the other hand, is slower because the indirection to
access values stored in the object list is more expensive than accessing
values stored in a vector.

Values (test_class and itest_class) and lists are created as explained
in the first section. Note that for pointer lists, the iteration will suffer
an extra indirection: they will need to access the test_class
values stored in another container:

Intrusive containers can offer performance benefits that cannot be achieved
with equivalent non-intrusive containers. Memory locality improvements are
noticeable when the objects to be inserted are small. Minimizing memory allocation/deallocation
calls is also an important factor and intrusive containers make this simple
if all objects to be inserted in intrusive containers are allocated using
std::vector or std::deque.