My lallocator<T>

O

Oliver S.

I was often annoyed about the performance of std::basic-string because
of complex memory-allocators working in the background to found the work
of std::allocator<T>. Basically, the performance doesn't reeach the per-
formance of a stack-based C-string in most cases. But nevertheless, the
comfort and the implicit protection against buffer-overflows makes std::
basic_string woth to be used. So I searched for a way to have my own
stack-based memory-allocation. I came up with the following idea which
resulted in a class I called the lallocator (derived from local alloca-
tor; this sounds funny in german because to "lall" means to babble in
german *g*).
First, we have the lallocator-class which is an interface to the locally
allocated storage for the stl-object (my lallocator can't be used for
strings only). This class is the same for all sizes of local stack-based
pools to prevent that we would compile derived stl-classes for any size
we use. Second, there's a class called lallocator_buffer_if which is the
interface to the local storage we allocated for the lallocator. I defined
this interface for all sizes of locally allocated pools to prevent any
secial-buffer-size-compiling I mentioned before. Third there's a dervied
class of lallocator_buffer_if<T>, called lallocator_buffer<T, buffers,
size>; T is the usual data-type in the buffers, buffers is the number of
buffers which can be allocated by the lallocator and size is the size of
each buffer. Whenever someone tries to do an allocate() on the lallocator
and there's a free buffer and the buffer is large enough to satisfy the
allocation-request, we'll allocate the buffer from the pool; otherwise
we fall back to std::allocator.

Here's an example of how this is used:

typedef std::basic_string<char, char_traits<char>, lallocator<char> >
lallostring;

lallocator_buffer<char, 5, 128> lbc;
lallocator<char> lallo( &lbc );
lallostring lsTest( lallo );

lsTest = "sdsdass";

Unfortunately my compiler isn't able to eat the following code
to prevent explicit instanciation of a lallocator<T>-object:

lallocator_buffer<char, 5, 128> lbc;
lallostring lsTest( lallocator<char>( &lbc ) );

Does anyone know if there's a conformance-problem here or is this
just a bug of my compiler?


So here's my lalloator (it isn't fully stl-conformant and I used a single
trick to prevent having an additional pointer in lallocator_buffer_if which
causes my code not to work on theoretical C++-implementations; sorry to all
religious developers):



template<typename T>
class lallocator_buffer_if
{
protected:
template<typename T, std::size_t buffers, std::size_t buffer_size>
friend class lallocator_buffer;

template<typename T>
friend class lallocator;

private:
lallocator_buffer_if() {}
T *pop_buffer();
void push_buffer( T *pt );
bool is_yours( T *pt );

protected:
union buffer_header
{
buffer_header *pbhNextFree;
T at[1];
};

protected:
std::size_t m_buffer_size;
buffer_header *m_pbhFirstFree;
buffer_header *m_pbhBufferEnd;
};

template<typename T>
inline
T *lallocator_buffer_if<T>::pop_buffer()
{
buffer_header *pbh;

if( (pbh = m_pbhFirstFree) == NULL )
return NULL;

return m_pbhFirstFree = pbh->pbhNextFree,
&pbh->at[0];
}


template<typename T>
inline
void lallocator_buffer_if<T>::push_buffer( T *pt )
{
buffer_header *pbh;

pbh = (buffer_header *)pt;
pbh->pbhNextFree = m_pbhFirstFree;
m_pbhFirstFree = pbh;
}

template<typename T>
inline
bool lallocator_buffer_if<T>::is_yours( T *pt )
{
return (void*)pt >= (void *)this &&
pt < &m_pbhBufferEnd->at[0];
}



template<typename T, std::size_t buffers, std::size_t buffer_size>
class lallocator_buffer : public lallocator_buffer_if<T>
{
public:
lallocator_buffer();

private:
union buffer
{
buffer_header bh;
T atUnReferenced[buffer_size];
};

private:
buffer aBuffers[buffers];
};

template<typename T, std::size_t buffers, std::size_t buffer_size>
inline
lallocator_buffer<T, buffers, buffer_size>::lallocator_buffer()
{
buffer *pbuf,
*pbufNext;

for( (pbuf = &aBuffers[buffers - 1],
pbufNext = NULL);
pbuf >= aBuffers;
(pbufNext = pbuf,
pbuf -= 1) )
pbuf->bh.pbhNextFree = &pbufNext->bh;

m_buffer_size = buffer_size;
m_pbhBufferEnd = &aBuffers[buffers].bh;
m_pbhFirstFree = &aBuffers[0].bh;
}



template<typename T>
class lallocator : public std::allocator<T>
{
public:
lallocator( lallocator_buffer_if<T> *plbi );
lallocator( lallocator const &lc );
~lallocator() {};
pointer allocate( size_type count, void *hint = NULL );
void deallocate( pointer ptr, size_type count );
lallocator &operator =( lallocator const &lc );

public:
template<class Other>
struct rebind
{
typedef lallocator<Other> other;
};

private:
lallocator_buffer_if<T> *m_plbi;
};

template<typename T>
inline
lallocator<T>::lallocator( lallocator_buffer_if<T> *plbi ) :
std::allocator<T>()
{
m_plbi = plbi;
}

template<typename T>
inline
lallocator<T>::lallocator( lallocator const &lc ) :
std::allocator<T>( lc )
{
m_plbi = lc.m_plbi;
}

template<typename T>
inline
typename lallocator<T>::pointer lallocator<T>::allocate( size_type count,
void *hint )
{
T *pt;

if( count > m_plbi->m_buffer_size ||
(pt = m_plbi->pop_buffer()) == NULL )
return std::allocator<T>::allocate( count );

return pt;
}

template<typename T>
inline
void lallocator<T>::deallocate( pointer ptr, size_type count )
{
if( !m_plbi->is_yours( ptr ) )
return (void)std::allocator<T>::deallocate( ptr, count );

m_plbi->push_buffer( ptr );
}

template<typename T>
inline
typename lallocator<T> &lallocator<T>::eek:perator =( lallocator const &lc )
{
return m_plbi = lc.m_plbi,
*this;
}




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

Greg

Oliver said:
I was often annoyed about the performance of std::basic-string because
of complex memory-allocators working in the background to found the work
of std::allocator<T>. Basically, the performance doesn't reeach the per-
formance of a stack-based C-string in most cases. But nevertheless, the
comfort and the implicit protection against buffer-overflows makes std::
basic_string woth to be used. So I searched for a way to have my own
stack-based memory-allocation. I came up with the following idea which
resulted in a class I called the lallocator (derived from local alloca-
tor; this sounds funny in german because to "lall" means to babble in
german *g*).
First, we have the lallocator-class which is an interface to the locally
allocated storage for the stl-object (my lallocator can't be used for
strings only). This class is the same for all sizes of local stack-based
pools to prevent that we would compile derived stl-classes for any size
we use. Second, there's a class called lallocator_buffer_if which is the
interface to the local storage we allocated for the lallocator. I defined
this interface for all sizes of locally allocated pools to prevent any
secial-buffer-size-compiling I mentioned before. Third there's a dervied
class of lallocator_buffer_if<T>, called lallocator_buffer<T, buffers,
size>; T is the usual data-type in the buffers, buffers is the number of
buffers which can be allocated by the lallocator and size is the size of
each buffer. Whenever someone tries to do an allocate() on the lallocator
and there's a free buffer and the buffer is large enough to satisfy the
allocation-request, we'll allocate the buffer from the pool; otherwise
we fall back to std::allocator.

Here's an example of how this is used:

typedef std::basic_string<char, char_traits<char>, lallocator<char> >
lallostring;

lallocator_buffer<char, 5, 128> lbc;
lallocator<char> lallo( &lbc );
lallostring lsTest( lallo );

lsTest = "sdsdass";

Unfortunately my compiler isn't able to eat the following code
to prevent explicit instanciation of a lallocator<T>-object:

lallocator_buffer<char, 5, 128> lbc;
lallostring lsTest( lallocator<char>( &lbc ) );

Does anyone know if there's a conformance-problem here or is this
just a bug of my compiler?


So here's my lalloator (it isn't fully stl-conformant and I used a single
trick to prevent having an additional pointer in lallocator_buffer_if which
causes my code not to work on theoretical C++-implementations; sorry to all
religious developers):
...

Actually, there is nothing wrong with the union of the buffer and the
pointer to the next free buffer. It follows the standard design for a
memory pool - which is what you have implemented. See boost's "pool"
library for another example of a memory pool.

Memory pools work best for constant sized memory allocations - while
std::string allocations will vary in size. But since std::string often
makes many small allocations, there can still be some benefit from
using a sufficiently large size for the memory pool blocks. An even
more effective optimization would to add a fixed-sized character buffer
data member to the string class itself. By so doing, the performance
for std::strings would equal that of stack-based strings for strings
shorter than a certain length.

Greg


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

Branimir Maksimovic

Oliver said:
I was often annoyed about the performance of std::basic-string because
of complex memory-allocators working in the background to found the work
of std::allocator<T>. Basically, the performance doesn't reeach the per-
formance of a stack-based C-string in most cases. But nevertheless, the
comfort and the implicit protection against buffer-overflows makes std::
basic_string woth to be used. So I searched for a way to have my own
stack-based memory-allocation. I came up with the following idea which
resulted in a class I called the lallocator (derived from local alloca-
tor; this sounds funny in german because to "lall" means to babble in
german *g*).


Here's an example of how this is used:

typedef std::basic_string<char, char_traits<char>, lallocator<char> >
lallostring;

lallocator_buffer<char, 5, 128> lbc;
lallocator<char> lallo( &lbc );
lallostring lsTest( lallo );

lsTest = "sdsdass";

Unfortunately my compiler isn't able to eat the following code
to prevent explicit instanciation of a lallocator<T>-object:

lallocator_buffer<char, 5, 128> lbc;
lallostring lsTest( lallocator<char>( &lbc ) );

Oh, this is just common error you have declared function.

template<typename T>
class lallocator : public std::allocator<T>
{
public:
lallocator( lallocator_buffer_if<T> *plbi );
lallocator( lallocator const &lc );
~lallocator() {};
pointer allocate( size_type count, void *hint = NULL );
void deallocate( pointer ptr, size_type count );
lallocator &operator =( lallocator const &lc );

public:
template<class Other>
struct rebind
{
typedef lallocator<Other> other;
};

private:
lallocator_buffer_if<T> *m_plbi;
};

This class needs default constructor for rebind purposes.
Ok, after correcting compiler errors here is your code:
you can make static pointer used for default constructor,
into a static thread local storage pointer if needed.
There is alwayspossibility that memory allocated with
one allocator could be freed with other, so you have
to be carefull not to set different buffers in a scope.

#include <string>
#include <cassert>
//using namespace std;


#ifndef __GNUG__
#define __thread /* this_would_be_a_great_thing_to_have */
#endif


template<typename T>
class lallocator_buffer_if
{
protected:
template<typename TT, std::size_t buffers, std::size_t buffer_size>
friend class lallocator_buffer;

template<typename TT>
friend class lallocator;

private:
lallocator_buffer_if() {}
T *pop_buffer();
void push_buffer( T *pt );
bool is_yours( T *pt );

protected:
union buffer_header
{
buffer_header *pbhNextFree;
T at[1];
};

protected:
std::size_t m_buffer_size;
buffer_header *m_pbhFirstFree;
buffer_header *m_pbhBufferEnd;

};

template<typename T>
inline
T *lallocator_buffer_if<T>::pop_buffer()
{
buffer_header *pbh;

if( (pbh = m_pbhFirstFree) == NULL )
return NULL;

return m_pbhFirstFree = pbh->pbhNextFree,
&pbh->at[0];

}

template<typename T>
inline
void lallocator_buffer_if<T>::push_buffer( T *pt )
{
buffer_header *pbh;

pbh = (buffer_header *)pt;
pbh->pbhNextFree = m_pbhFirstFree;
m_pbhFirstFree = pbh;

}

template<typename T>
inline
bool lallocator_buffer_if<T>::is_yours( T *pt )
{
return (void*)pt >= (void *)this &&
pt < &m_pbhBufferEnd->at[0];

}

template<typename T, std::size_t buffers, std::size_t buffer_size>
class lallocator_buffer : public lallocator_buffer_if<T>
{
public:
lallocator_buffer();

private:
union buffer
{
typename lallocator_buffer_if<T>::buffer_header bh;
T atUnReferenced[buffer_size];
};

private:
buffer aBuffers[buffers];

};

template<typename T, std::size_t buffers, std::size_t buffer_size>
inline
lallocator_buffer<T, buffers, buffer_size>::lallocator_buffer()
{
buffer *pbuf,
*pbufNext;

for( (pbuf = &aBuffers[buffers - 1],
pbufNext = NULL);
pbuf >= aBuffers;
(pbufNext = pbuf,
pbuf -= 1) )
pbuf->bh.pbhNextFree = &pbufNext->bh;

this->m_buffer_size = buffer_size;
this->m_pbhBufferEnd = &aBuffers[buffers].bh;
this->m_pbhFirstFree = &aBuffers[0].bh;

}

template<typename T>
class lallocator : public std::allocator<T>
{
public:
typedef typename std::allocator<T>::pointer pointer;
typedef typename std::allocator<T>::size_type size_type;
lallocator():m_plbi(m_buffer) { assert(m_plbi); }
lallocator( lallocator_buffer_if<T> *plbi );
lallocator( lallocator const &lc );
~lallocator() {};
pointer allocate(size_type count, void *hint = NULL );
void deallocate( pointer ptr, size_type count );
lallocator &operator =( lallocator const &lc );

static void set_buffer(lallocator_buffer_if<T>* b)
{
m_buffer=b;
}

public:
template<class Other>
struct rebind
{
typedef lallocator<Other> other;
};

private:
lallocator_buffer_if<T> *m_plbi;
static __thread lallocator_buffer_if<T>* m_buffer;
};

template <typename T>
__thread lallocator_buffer_if<T>* lallocator<T>::m_buffer=0;

template<typename T>
inline
lallocator<T>::lallocator( lallocator_buffer_if<T> *plbi ):
std::allocator<T>()
{
m_plbi = plbi;

}

template<typename T>
inline
lallocator<T>::lallocator( lallocator const &lc ) :
std::allocator<T>( lc )
{
m_plbi = lc.m_plbi;

}

template<typename T>
inline
typename lallocator<T>::pointer lallocator<T>::allocate( size_type
count, void *hint )
{
T *pt;

if( count > m_plbi->m_buffer_size ||
(pt = m_plbi->pop_buffer()) == NULL )
return std::allocator<T>::allocate( count );

return pt;

}

template<typename T>
inline
void lallocator<T>::deallocate( pointer ptr, size_type count )
{
if( !m_plbi->is_yours( ptr ) )
return (void)std::allocator<T>::deallocate( ptr, count );

m_plbi->push_buffer( ptr );

}

template<typename T>
inline
lallocator<T>& lallocator<T>::eek:perator =( lallocator<T> const &lc )
{
return m_plbi = lc.m_plbi,
*this;

}

int main()
{
typedef std::basic_string<char, std::char_traits<char>,
lallocator<char> >
lallostring;

lallocator_buffer<char, 5, 128> lbc;
lallocator<char> lallo( &lbc );
lallocator<char>::set_buffer(&lbc);
lallostring lsTest( lallo );

lsTest = "sdsdass";
{
lallocator_buffer<char, 5, 128> lbc;
lallocator<char>::set_buffer(&lbc);
lallostring lsTest ((lallocator<char>( &lbc
)));
lsTest+="abc"+lallostring("def");
}
}

Greetings, Bane.


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

kuyper

Oliver S. wrote:
...
Unfortunately my compiler isn't able to eat the following code

That isn't very specific. It would help a great deal if you indicated
what, precisely, your compiler said was indigestible about this code.

...
So here's my lalloator (it isn't fully stl-conformant and I used a single
trick to prevent having an additional pointer in lallocator_buffer_if which
causes my code not to work on theoretical C++-implementations; sorry to all
religious developers):

Your use of the words "theoretical" and "religious" implies that you
think the non-conformance is unimportant. It might be; but you should
seriously consider the possibility that the problems you're having may
in fact be due to that non-conformance. In particular, implementations
are allowed to assume that all instances of a given allocator type are
equivalent (20.1.5p4), which isn't the case for your allocators. While
the standard encourages implementators to create implementations that
don't rely on that assumption, many of them do in fact take advantage
of that option, for instance by not actually copying allocators when
they have the same type.

std::list<T,Allocator>::splice() is particularly difficult to implement
efficiently unless you build in an assumption that all instances of
Allocator are equivalent.


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

Oliver S.

Actually, there is nothing wrong with the union of the buffer and the
pointer to the next free buffer. ...

That's not the in-conformance I thought about. Look at is_yours in the
base-class and you can find this in-conformance which might offense some
religious C++ers.
Memory pools work best for constant sized memory allocations ...

Mine does work equally good for everything up to the limit for a buffer.
An even more effective optimization would to add a fixed-sized character
buffer data member to the string class itself.

Maybe, but this wouldn't be a genral-purpose-string anymore because
every string would carry around a buffer that might not be used.

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

Oliver S.

In this paper on Using Stateful Allocator with the STL there is some
code that I've used on a number of platforms, with all the major STL
versions ...

Nearly the same idea, but very low-levelish in your flavour.
I think mine is a bit more comfortable.

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

Oliver S.

While the standard encourages implementators to create implementations
that don't rely on that assumption, many of them do in fact take advantage
of that option, for instance by not actually copying allocators when
they have the same type.

I intentionally missed a default-constructor to prevent that and thereby
enforce a compilation-error.

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

kuyper

Oliver said:
That's not the in-conformance I thought about. Look at is_yours in the
base-class and you can find this in-conformance which might offense some
religious C++ers.

I'm not sure I follow that; in what sense is it non-conformant? I'm
sure it will be obvious two minutes after I post this message, but
right now it isn't.


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

Greg Herlihy

Oliver said:
That's not the in-conformance I thought about. Look at is_yours in the
base-class and you can find this in-conformance which might offense some
religious C++ers.

I'm not sure why is_yours() compares the pointer against the pointer to
the buffer. Comparing it against the first item in the buffer's array
would certainly be a valid test:

template<typename T>
inline
bool lallocator_buffer_if<T>::is_yours( T *pt )
{
Mine does work equally good for everything up to the limit for a buffer.

Using fixed sized memory blocks to speed up variably-sized allocations
has two principal shortcomings: oversized allocations see no benefit,
while undersized allocations waste memory.
Maybe, but this wouldn't be a genral-purpose-string anymore because
every string would carry around a buffer that might not be used.

The string with an internal character buffer would still be general
purpose - oversized strings would continue to allocate their character
buffer dynamically. An unused internal character buffer wastes no more
memory than the memory wasted by deallocated a fixed-sized buffer.
After all, deallocated buffers are never really "freed" (that is,
returned to the general memory pool where it can be used to fulfill any
subsequent memory allocation request). Rather freed buffers are held in
reserve just in case a future string allocation could use it. A string
with an internal buffer on the other hand, returns the buffer to the
general purpose memory pool (or the stack) upon its destruction.

A further advantage that an internal character buffer has over external
storage is that it adds no meaningful delay when constructing or
copying a string object. The entire internal character buffer does not
need to be initialized or copied with the string, just the portion that
contains the string's characters.

Greg


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

Oliver S.

I'm not sure I follow that; in what sense is it non-conformant? I'm
sure it will be obvious two minutes after I post this message, but
right now it isn't.

I assume the data for the buffers to be placed after the data of the
base-class; this might not be true for theoretical C++-implementations.
 
O

Oliver S.

> Oh, this is just common error you have declared function.

Ok, these seem to have priority over variable-definitions.
> This class needs default constructor for rebind purposes.

I won't supply a default-constructor because I want the STL-classes used with
my lallocator<T> to work with the pools attached to the allocator-objects. The
allocator-objects usually stored internally in the STL-container-classes are
typically rebind'ed versions of the supplied allocator-type to allow for the
allocators supplied to the constructor of the stl-container to be of a dif-
ferent type. These allocator-objects are created through a copy-constructor
I supply. As the major STL-implementations are prepared for stateful alloca-
tors as far as possible, this works.
 
K

kuyper

Oliver S. said:
I assume the data for the buffers to be placed after the data of the
base-class; this might not be true for theoretical C++-implementations.

OK - I can see the issue now. I hadn't noticed that assumption. You're
right - Section 10 paragraph 3 indicates that you can't rely on such an
assumption. You could remove that assumption by using a pointer to the
entire derived object, converted to void*, rather than a pointer to the
base class sub-object.
 
B

Branimir Maksimovic

Oliver said:
I won't supply a default-constructor because I want the STL-classes used with
my lallocator<T> to work with the pools attached to the allocator-objects. The
allocator-objects usually stored internally in the STL-container-classes are
typically rebind'ed versions of the supplied allocator-type to allow for the
allocators supplied to the constructor of the stl-container to be of a dif-
ferent type. These allocator-objects are created through a copy-constructor
I supply.

How? On gcc 3.4.3 and 3.4.2 string requires default constructor in
allocator because it probably have some internal data structure?
Since rebinded allocator have different type then one passed in
constructor string doesn't even try to use copy constructor?
That creates big problem because string can do something like this:
//......
~string()
{
buf->alloc.deallocate(ptr->buf);
typename Allocator<T>::rebind<MyStruct>::eek:ther alloc;
alloc.deallocate(ptr); // my example will crash in this case
}
it would be great if string have to use:
typename Allocator<T>::rebind<MyStruct>::eek:ther alloc(buff->alloc);
but it does not, I guess, at least gcc string.

Greetings, Bane.
 
K

kuyper

Branimir said:
How? On gcc 3.4.3 and 3.4.2 string requires default constructor in
allocator because it probably have some internal data structure?
Since rebinded allocator have different type then one passed in
constructor string doesn't even try to use copy constructor?

Section 21.3.1 says that "In all basic_string constructors, a copy of
the Allocator argument is used for any memory allocation performed by
the constructor or member functions during the lifetime of the object."
Section 21.3 says the same thing about standard containers.

Granted, many of the requirements imposed on strings and containers can
be satisfied by non-member operator overloads, and those are permitted
to make use of the fact that the allocator type is required to have a
default constructor.

An implementation is allowed to assume that all instances of the
allocator type are equivalent, which means that the default constructed
allocator can be assumed to be equivalent to a copy constructed one.
However, the standard encourages implementors to avoid relying upon
that assumption.
 
O

Oliver S.

Granted, many of the requirements imposed on strings and containers can
be satisfied by non-member operator overloads, and those are permitted
to make use of the fact that the allocator type is required to have a
default constructor.

Ok, but this isn't really a cosntraint for me because of the following
reason: "Global" string operators are usually slow because they generate
unnecessary temporary objects. And why should someone use a performance
-enhancing allocator primarily designed for container objects placed on
the stack *and* use global operators on strings using these allocators?
 
K

kuyper

Oliver S. said:
Ok, but this isn't really a cosntraint for me because of the following
reason: "Global" string operators are usually slow because they generate
unnecessary temporary objects. And why should someone use a performance
-enhancing allocator primarily designed for container objects placed on
the stack *and* use global operators on strings using these allocators?

The most likely reason for doing something like that is because the
string/container class that they are instantiating with your
performance-enhancing allocator was written by someone with other
objectives.
 
O

Oliver S.

The most likely reason for doing something like that is because
the string/container class that they are instantiating with your
performance-enhancing allocator was written by someone with other
objectives.


Why should someone not see this objective when the only
purpose of my allocator is to enhance the performance.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,521
Members
44,995
Latest member
PinupduzSap

Latest Threads

Top