Boost logo

Boost :

Subject: Re: [boost] Proposal: Monotonic Containers - Comparison with boost::pool, boost::fast_pool and TBB
From: Christian Schladetsch (christian.schladetsch_at_[hidden])
Date: 2009-06-23 15:46:21


On Wed, Jun 24, 2009 at 3:49 AM, Stewart, Robert <Robert.Stewart_at_[hidden]>wrote:

> Simonson, Lucanus J wrote
> On Tuesday, June 23, 2009 11:38 AM
> >
> > For multiple threads to
> > use the allocator without interacting (we would prefer not to
> > pay for locks if we aren't actually sharing information
> > between threads) the allocator must differ by type. The only
> > way that can be accomplished is if every thread calls a
> > different function with a diferent instantiation of the
> > allocator. We don't want to instantiate dozens of copies of
> > the same functions at compile time to support many threads.
>
> Only the storage need be unique in each case. Factor out the code that
> doesn't depend upon the tag type -- the behavior -- from that which does.
>
> That said, it would be painful to create unique tags for each thread. Is
> it a problem for the allocator to look in TLS for that thread's storage?

I have thought on this and refactored the allocator signature to deal with
both of these issues:

template <
   class T
   , class Region = default_region_tag
    , class Access = default_access_tag>
struct allocator;

The basic usage is:

struct region0 {};
struct region1 {};
struct region2 {};

BOOST_AUTO_TEST_CASE(test_shared_allocation)
{
    // use default region and access
    std::list<int, monotonic::allocator<int> > list;

    // use specific region and access
    std::list<int, monotonic::allocator<int, region0,
monotonic::shared_access_tag> > list;
    std::list<int, monotonic::allocator<int, region0,
monotonic::thread_local_access_tag> > list;
    // equivalently:
    std::list<int, monotonic::shared_allocator<int, region0> > list;
    std::list<int, monotonic::thread_local_allocator<int, region0> > list;

    // equivalently, using wrapped container types defined in
monotonic/container/*.hpp:
    monotonic::list<int> list;
    monotonic::list<int, region0, monotonic::shared_access_tag> list;
    monotonic::list<int, region1, monotonic::thread_local_access_tag> list;

    // use different regions
    monotonic::map<int, monotonic::list<monotonic::string<region2>,
region1>, region0> map;
}

See https://svn.boost.org/svn/boost/sandbox/monotonic/boost/monotonic/

This deals with allocation from different regions based on a tag-type, and
also allows the user to specifiy what access type to use.

The remaining issue is that of local storage:

BOOST_AUTO_TEST_CASE(test_local)
{
    monotonic::local<region0> storage0;
    monotonic::local<region1> storage1;
    {
        std::list<int, monotonic::allocator<int, region0> > list0;
        std::list<int, monotonic::allocator<int, region1> > list1;
        fill_n(back_inserter(list0), 100, 42);
        fill_n(back_inserter(list1), 100, 42);

        std::basic_string<char, std::char_traits<char>,
monotonic::allocator<char, region0> > str("foo");
        str += "bar";
    }
} // region0 and region1 will be reset automatically

Now, in this last case, it can be said that this is not re-entrant which is
correct. However, there is a simple idiom that can be used:

template <class Storage>
void Reenter(Storage &store, size_t count)
{
    if (count-- == 0) return;

    // use the Storage type to make new allocators
    typedef std::basic_string<char, std::char_traits<char>, typename
Storage::template allocator<char>::type> String;
    std::list<String, typename Storage::template allocator<String>::type>
list;
    /// ... use list and string types... they will use the storage correctly

    // can also use storage directly:
    char *bytes = store.allocate_bytes<123>();
    string &str = store.create<string>("foo");
    array<int, 300> &nums = store.create<array<int, 300> >();

    store.destroy(str);

    Reenter(store, count);
}

struct my_local_region {};
void Start()
{
     monotonic::local<my_local_region/*, access_type_tag*/> storage;
     Reenter(storage, 500);
} // storage is reset on exit

Of course, Start() is still not reentrant. It can't be, because it uses a
static global. However, Reenter() is. It's the best that we can do.

I did a test that used this idiom over using a normal allocator and thrashed
a list of strings. The monotonic answer was twice the speed. Sure, it is
more work, but twice the speed is twice the speed.

I am interested to hear what people think of the new allocator model with
tags for Region and Access, and the local model that is based on tags as
well.

Regards,
Christian.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk