Thoughts on memory allocation...

Stefan Olsson stefan@noname4us.com
Thu Jun 27 15:45:00 GMT 2002


Hi all!

Remember a couple of questions raised a few weeks ago related to the 
libstdc++ allocators and ideas on improvements etc?

Back then I was thinking of just writing down all the ideas, so I set 
out to read up on the subject and try to understand what others have 
done, what might be useful or not and ended up with a nice little heap 
of paper (Yes, I do like to read some stuff of paper still...) on my 
desk. Just writing it all down into a new document seemed somewhat 
redundant to what others have done so many times so I made a few design 
decisions:

- Try to do as few system (i.e. malloc) calls as possible to gain 
similar performance across various platforms.

- Since most of our applications are long running ones (application 
servers and such) keep the heap growth (due to fragmentation etc) to a 
minimum.

- Don't bother about memory consumption if needed to accomplish the 
previous task (I don't like to waste memory, but on the targets that we 
are working on memory is not that much of an issue).

- Improve thread concurrency

- "Solve" the "one thread allocates and anotherone freeing" memory issue 
that I discussed earlier (i.e. what happens when keeping a cache of data 
in a MT application)

After figuring out how to implement this (in terms of integrating with 
libstdc++) i decided to make a header and implementation file that is 
linked into the application to solve the "one instance" issue of having 
actual code in .h files (I'm sure there is a much better way of doing 
much of the implementation here, but once again: this is posted for 
discussion...) and then tell the containers to use this by wrapping it 
through the __allocator<> adapter available in stl_alloc.h (i.e. vector< 
int, __allocator< void, nn4us::nn4us_alloc > > foo;)

Oki, the source is obviously the best documentation (?) but here are 
some comments...
nn4us_alloc.hpp:

- There seems to something "missing" in the __allocator<> code since I 
had to add the operator == and != in order to get basic_string<> to 
compile, this does however not happen if I try to compile with 
malloc_allocator which is part of stl_alloc.h - why I don't know...

- There are defintions for new and delete as well...
...as I said above, this piece of code is here for discussion and the 
best way I could think of to get an entire application to behave similar 
on several plattforms as well as minimizing heap growth was to have new 
and delete use the same allocator as the containers.

nn4us_alloc.cpp
- The allocator gets chunks (CHUNK_SIZE) of memory from the system.

- Each size (up to the predetermined MAX_BYTES) are delt with by the 
allocator, the rest is sent to malloc()/free() directly.

- All requests are rounded to powers of two - even the ones that are 
larger than MAX_BYTES in order to minimize fragmentation in malloc() 
freelists.

- Each size (power of two) has it's own list of records and it's own 
mutex. This is done to improve concurrency (i.e. two requests of 
diffrent sizes can be delt with without locking if the threads already 
owns a record). What might seem as counterproductive to this is the 
malloc_mutex which actually syncronizes all malloc() calls...
...once again this is done for testing and was added after reading a 
paper on the Linux malloc() implementation that states "To reduce lock 
contention, ptmalloc searchs for the first unlocked subheap and grabs 
memory from it to fulfill a malloc() request. If ptmalloc doesnt find an 
unlocked heap, it creates a new one." (A very interesting read: 
http://www.citi.umich.edu/techreports/reports/citi-tr-00-5.pdf). My 
interpertation, and the goal with this experiment was to see if 
"helping" malloc() to avoid creating new heap would improve 
overall/longterm performance.

- All records are initially "owned" by a fiction thread id 0 (each 
thread is assigned a unique id by setting a key. BTW Does anyone know 
what the posix standard says about assigning the id returned by 
pthread_self()? I have tried to find this but with no luck and the 
source code did not make any sense to me...) When a thread requests 
memory the owner on this record is set to this thread id which means 
that subsequent deallocate calls can be done without locking the list 
and later on reused without locking. The "trick" with this is that we 
can now see if the thread that is deallocating the memory also was 
allocating it - if not we lock and return it to thread id 0 which 
effectively reduces the problems of "unbounded heap growth" with the 
current pthread_alloc implementation.

- In order to use the same set of methods as well as memory, the new and 
delete calls rely on the size being part of the actual data...
...see the implementation - it does not make much sense to write more 
about it here ;-)

NOTE! Some of the comments in the source may be a bit confusing since 
some parts have been rewritten a couple of times for testing ;-)

So where did this leave us? Well we have done quite a few tests, both on 
our real applications and on small testprograms. It seems that the goals 
where obtained even though this rough implementation does leave a lot to 
be tuned (see below) and fixed - but our main problem (fragmentation 
over time and the inabillity to use the __default_alloc_template due to 
concatenation) has more or less completly vanished and a small 
testprogram (which creates 10 threads, each of which are inserting 
values into a map<int,int> and then clearing the map over and over) 
shows that with malloc_allocator as index 1, the default 
(__default_alloc_template) allocator gains less than 10% on both single 
and dual processor x86 machines whilst this implementation improves 
performance with well over 100% on single processor machines and even 
more on dual processor machines...

Things that needs to be looked into is obviously cacheline behaviour etc 
but I thought I post this now and see if anyone find this of interest at 
all? I feel that some of the ideas (which aren't new but still...) could 
be added to the existing allocators to reduce fragmentation (i.e. fized 
size/power of two) in libstdc++ based applications...

Looking forward to your comments!

Brgds

/Stefan

-- 
Life is like a 10 speed bicycle.  Most of us have gears we never use.

-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: nn4us_alloc.hpp
URL: <http://gcc.gnu.org/pipermail/libstdc++/attachments/20020627/22a9bf74/attachment.ksh>
-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: nn4us_alloc.cpp
URL: <http://gcc.gnu.org/pipermail/libstdc++/attachments/20020627/22a9bf74/attachment-0001.ksh>


More information about the Libstdc++ mailing list