From: Raoul Gough on
I guess a fairly standard trick when implementing a custom free-list
allocator is to re-use the start of the user-data block for the "next"
pointer of the free list. This could look something like the following

template<typename T>
void allocator<T>::deallocate(void* userData)
{
// Assume T is big enough for a void*. Store the link to the next
// free block
*static_cast<void**>(userData) = freeList_;

// Make this block the head of freeList
freeList_ = userData;
}

I'm not sure if that's the exact signature for a standard allocator,
but I think the idea is clear. Unfortunately, this is nonportable
because it can violate strict aliasing. Assume that we have
allocator<int>, and we do something like this:

int Foo::bar(int* data)
{
int result = *data;
allocator_.deallocate(data);
return result;
}

When the compiler inlines the deallocate function, we end up with
this:

int result = *data;
*static_cast<void **>(data) = allocator_.freeList_;

So we have two aliases to *data, one of which is int& and one of which
is void*&. We're trying to re-use the same location to store an int
*and* a void* at the same time. I remember seeing a case (with g++ and
its strict-aliasing optimizations) where the compiler reordered the
load and store of *data, meaning that the free list pointer overwrites
the user data before the user data gets read.

The example should probably destroy the int object as below, but from
memory this didn't affect the behaviour in the particular case I
saw...

int result = *data;
data->~int();
allocator_.deallocate(data);

So I guess I have a few questions, firstly should the version with the
destructor work portably, and if not, is there a portable way to make
it work? How about if it doesn't call the destructor?

--
Cheers,
Raoul.

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Chris Thomasson on
"Raoul Gough" <RaoulGough(a)yahoo.co.uk> wrote in message
news:c12cbf5c-f861-4fbb-a5d5-a40298f32dd2(a)q1g2000prf.googlegroups.com...
>I guess a fairly standard trick when implementing a custom free-list
> allocator is to re-use the start of the user-data block for the "next"
> pointer of the free list. This could look something like the following
>
> template<typename T>
> void allocator<T>::deallocate(void* userData)
> {
> // Assume T is big enough for a void*. Store the link to the next
> // free block
> *static_cast<void**>(userData) = freeList_;
>
> // Make this block the head of freeList
> freeList_ = userData;
> }
[...]

Well, you could use "something" like:


<code-sketch which should compile>
_________________________________________________________________
#include <cstdio>


template<typename T>
class allocator {
union node {
T m_obj;
node* m_next;
};

node* m_head;
unsigned m_count;
unsigned const m_maxdepth;

public:
allocator(unsigned maxdepth = 100)
: m_head(NULL), m_count(0), m_maxdepth(100) {
}

~allocator() {
node* n = m_head;
while (n) {
node* next = n->m_next;
std::printf("(%p)-delete\n", (void*)n);
delete n;
n = next;
}
}

public:
T* create() {
node* n = m_head;
if (! n) {
n = new node;
std::printf("(%p)-cache miss\n", (void*)n);
} else {
--m_count;
m_head = n->m_next;
std::printf("(%p)-cache hit\n", (void*)n);
}
return &n->m_obj;
}

void destroy(T* const obj) {
node* const n = reinterpret_cast<node*>(obj);
if (m_count < m_maxdepth) {
n->m_next = m_head;
m_head = n;
++m_count;
std::printf("(%p)-cached\n", (void*)n);
} else {
delete n;
std::printf("(%p)-delete\n", (void*)n);
}
}
};



#define DEPTH() 6
int main() {
{
int i;
char* ptrs[DEPTH()] = { NULL };
allocator<char> alloc;
for (i = 0; i < DEPTH(); ++i) {
ptrs[i] = alloc.create();
}
for (i = 0; i < DEPTH(); ++i) {
alloc.destroy(ptrs[i]);
}
for (i = 0; i < DEPTH(); ++i) {
ptrs[i] = alloc.create();
}
for (i = 0; i < DEPTH(); ++i) {
alloc.destroy(ptrs[i]);
}
}

{
int i;
double* ptrs[DEPTH()] = { NULL };
allocator<double> alloc;
for (i = 0; i < DEPTH(); ++i) {
ptrs[i] = alloc.create();
}
for (i = 0; i < DEPTH(); ++i) {
alloc.destroy(ptrs[i]);
}
for (i = 0; i < DEPTH(); ++i) {
ptrs[i] = alloc.create();
}
for (i = 0; i < DEPTH(); ++i) {
alloc.destroy(ptrs[i]);
}
}

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
std::puts("\n\n--------------------------------------------\n\
Press <ENTER> to exit...");
std::getchar();
return 0;
}

_________________________________________________________________



Does that help you at all?


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Chris Thomasson on

"Chris Thomasson" <cristom(a)comcast.net> wrote in message
news:PZadnX7vCIZza2banZ2dnUVZ_ualnZ2d(a)comcast.com...
> "Raoul Gough" <RaoulGough(a)yahoo.co.uk> wrote in message
> news:c12cbf5c-f861-4fbb-a5d5-a40298f32dd2(a)q1g2000prf.googlegroups.com...
>>I guess a fairly standard trick when implementing a custom free-list
>> allocator is to re-use the start of the user-data block for the "next"
>> pointer of the free list. This could look something like the following
>>
>> template<typename T>
>> void allocator<T>::deallocate(void* userData)
>> {
>> // Assume T is big enough for a void*. Store the link to the next
>> // free block
>> *static_cast<void**>(userData) = freeList_;
>>
>> // Make this block the head of freeList
>> freeList_ = userData;
>> }
> [...]
>
> Well, you could use "something" like:
>
>
> <code-sketch which should compile>
> _________________________________________________________________
[...]
> _________________________________________________________________
>
>
>
> Does that help you at all?

That was a very simple sketch I typed in the newsreader; it seems to compile
just fine, but has some problems. One, it does not call any dtor until a
cache overflow, or allocator destruction. It does not call ctors on every
allocation. It has a bug in the ctor:

allocator(unsigned maxdepth = 100)
: m_head(NULL), m_count(0), m_maxdepth(100) {
}

should be:

allocator(unsigned maxdepth = 100)
: m_head(NULL), m_count(0), m_maxdepth(maxdepth) {
}

of course.



Anyway, I think this should hopefully give you a __very_rough__ idea on how
to proceed... I guess the code as-is would be analogous to a cache
allocator...

;^)


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Raoul Gough on
On Apr 9, 4:43 am, "Chris Thomasson" <cris...(a)comcast.net> wrote:
> "Raoul Gough" <RaoulGo...(a)yahoo.co.uk> wrote in message
>
> news:c12cbf5c-f861-4fbb-a5d5-a40298f32dd2(a)q1g2000prf.googlegroups.com...>I guess a fairly standard trick when implementing a custom free-list
> > allocator is to re-use the start of the user-data block for the "next"
> > pointer of the free list. This could look something like the following
>
> > template<typename T>
> > void allocator<T>::deallocate(void* userData)
> > {
> > // Assume T is big enough for a void*. Store the link to the next
> > // free block
> > *static_cast<void**>(userData) = freeList_;
>
> > // Make this block the head of freeList
> > freeList_ = userData;
> > }
>
> [...]
>
> Well, you could use "something" like:
[snip]
> union node {
> T m_obj;
> node* m_next;
> };
[snip]
> void destroy(T* const obj) {
> node* const n = reinterpret_cast<node*>(obj);
> if (m_count < m_maxdepth) {
> n->m_next = m_head;
> m_head = n;
[snip]

This code has a significant restriction that the original code did
not: it only works for types T that can be stored in a union. From
section 9.5 of the '98 standard:

"An object of a class with a non-trivial constructor (12.1), a non-
trivial copy constructor (12.8), a non-trivial destructor (12.4), or a
non-trivial copy assignment operator (13.5.3, 12.8) cannot be a member
of a union"

Also, I'm not entirely sure that it addresses the fundamental aliasing
problem of reusing the same storage. Effectively, we'd end up with the
following inlined code:

int foo(int* p)
{
int result = *p;
reinterpret_cast<union_type*>(p)->m_next = m_head;

Interestingly perhaps, what we *don't* have is this:

int foo(union_type* p)
{
int result = p->m_obj;
p->m_next = m_head;

where it's clearer that the two expressions reference the same (union)
type and might alias the same (union) object. I'm not sure about the
exact semantics of unions, so I'm a little uncertain whether this
actually solves the basic problem (even for the restricted cases where
it would compile).

--
Cheers,
Raoul.

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Chris Thomasson on
"Raoul Gough" <RaoulGough(a)yahoo.co.uk> wrote in message
news:1a2ee2eb-6db0-4ff2-a82f-b64bd5c5c310(a)s13g2000prd.googlegroups.com...
> On Apr 9, 4:43 am, "Chris Thomasson" <cris...(a)comcast.net> wrote:
>> "Raoul Gough" <RaoulGo...(a)yahoo.co.uk> wrote in message
>>
>> news:c12cbf5c-f861-4fbb-a5d5-a40298f32dd2(a)q1g2000prf.googlegroups.com...>I
>> guess a fairly standard trick when implementing a custom free-list
>> > allocator is to re-use the start of the user-data block for the "next"
>> > pointer of the free list. This could look something like the following
>>
>> > template<typename T>
>> > void allocator<T>::deallocate(void* userData)
>> > {
>> > // Assume T is big enough for a void*. Store the link to the next
>> > // free block
>> > *static_cast<void**>(userData) = freeList_;
>>
>> > // Make this block the head of freeList
>> > freeList_ = userData;
>> > }
>>
>> [...]
>>
>> Well, you could use "something" like:
> [snip]
>> union node {
>> T m_obj;
>> node* m_next;
>> };
> [snip]
>> void destroy(T* const obj) {
>> node* const n = reinterpret_cast<node*>(obj);
>> if (m_count < m_maxdepth) {
>> n->m_next = m_head;
>> m_head = n;
> [snip]
>
> This code has a significant restriction that the original code did
> not: it only works for types T that can be stored in a union. From
> section 9.5 of the '98 standard:
[...]

You could always use:

struct node {
T m_obj;
node* m_next;
};


Are you going for an allocator that is 100% standard?

http://groups.google.com/group/comp.arch/browse_frm/thread/24c40d42a04ee855


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]