From: Ersek, Laszlo on
In article <87d3z6r36g.fsf(a)fever.mssgmbh.com>, Rainer Weikusat <rweikusat(a)mssgmbh.com> writes:
> Eric Sosman <esosman(a)ieee-dot-org.invalid> writes:
>> On 3/14/2010 4:40 PM, Rainer Weikusat wrote:
>>>
>>> Realistically, memory leaks are especially common in code which lacks
>>> any kind of memory managment, IOW, just uses malloc/ free naively.

This is two statements:

(1) "memory leaks are especially common in code which lacks any kind of
memory managment",

(2) using malloc/free naively is equivalent to no memory management at
all ("lacks any kind of").

A third statement might come as a consequence,

(3) "memory leaks are especially common in code which [...] just uses
malloc/ free naively"

I tend to accept (1) -- not free()ing consistently can be considered a
lack of memory management. I strongly disagree with (2) -- see below. I
tend to accept (3), but not because it follows from (1)&&(2) (I think
(2) is false) -- (3) is probably true because naive malloc()/free() is
*hard*.


> [...] There are,
> however, three reasons why I consider mine to be 'sufficiently
> representative':
>
> - A memory leak occurs whenever space allocated in order to
> accomplish a particular task isn't 'freed' before the
> pointer pointing to it is gone. Usually, this is an
> oversight on part of the person who wrote the code. The
> chances that a human forgets something increases with the
> number of different somethings which must be
> remembered. Code which has been written with an 'just call
> malloc whenever you need a buffer'-attitude will contain
> lots of malloc calls and this means there are a lot of
> opportunities to forget to also call free.

An approach with ownership responsibilities laid out clearly and
strictly can still be called naive. That stuff can be drawn up in all
kinds of diagrams, commented on in the code etc. I interpret "naive" as
"not sophisticated performance-wise", ie. the style in which, whenever
the coder can prove he *must* free a block, he/she frees it. For less or
more naivety, s/must/can/, even. "Naive" carries no negative meaning in
this context (correctness-wise, which we are talking about, wrt. memory
leaks).


> - Usually, people who are having problems they cannot deal
> with anymore manually tend to rather write tools to help
> them with being able to continue to act in problem-creating
> ways than to consider changing their habits. Valgrind is
> such a tool and it has excellent support for 'straight'
> malloc/free and basically no support for anything else.

I explicitly said I am not endorsing programming *for* valgrind -- I
expected such a followup. I wanted to present a point of view, one which
I happen to find secondary at best, but the priorities may be different
for the OP. A QA lab may require valgrindability, for example. I do test
my code with valgrind occasionally too, as an afterthought, after
writing my code as if the universe knew of no valgrind or anything
similar.


> - Custom allocators (eg as in, Apache2, Postgres or Samba) are
> often specifically designed to enable easy 'freeing' of
> memory which is no longer in use.
>
> Two common solutions suggest that a problem actually exists.

The problem does exist, and since I accept (3), I don't debate such
custom allocators decrease the risk of memory leaks. However, in my
opinion, they do so becase they ease the hardship that goes with manual
malloc()/free(), not because they provide something that is unavailable
naively.

(On a side note, you chose to ignore my earlier question (or so it
seems) targeted at the portability of those 50-300 SLOC custom
allocators and whether they are based on malloc().)


If I created any straw man arguments, I apologize -- they're due to lack
of reasoning skills and/or attention, not malice.

lacos
From: William Ahern on
Ersek, Laszlo <lacos(a)ludens.elte.hu> wrote:
> In article <87d3z6r36g.fsf(a)fever.mssgmbh.com>, Rainer Weikusat <rweikusat(a)mssgmbh.com> writes:
<snip>
> > - Usually, people who are having problems they cannot deal
> > with anymore manually tend to rather write tools to help
> > them with being able to continue to act in problem-creating
> > ways than to consider changing their habits. Valgrind is
> > such a tool and it has excellent support for 'straight'
> > malloc/free and basically no support for anything else.
>
> I explicitly said I am not endorsing programming *for* valgrind -- I
> expected such a followup. I wanted to present a point of view, one which
> I happen to find secondary at best, but the priorities may be different
> for the OP. A QA lab may require valgrindability, for example. I do test
> my code with valgrind occasionally too, as an afterthought, after
> writing my code as if the universe knew of no valgrind or anything
> similar.

Perhaps you're giving too much ground on the point. I don't think there's
anything wrong with adding code _for_ Valgrind, and I encourage it. All of
my specialized allocators have debug modes which allow Valgrind to catch
overflows (underflows are harder) and memory leaks.

I write code which explicitly frees all objects--at least in debug
mode--rather than depending on the allocator to do this, on the premise that
if my code is losing track of an object something is likely amiss. Often a
programmer makes the "premature optimization", especially in corner cases
such as unwinding intermediate commitments after an error, that it's okay to
lose track of an object because the allocator keeps a reference, neglecting
altogether the possible security ramifications or the side-effect of losing
possible signals on code correctness.

I also fix my code and submit patches to other projects when Valgrind
complains, _even_ _if_ the complaints don't actually signify a real error.
Valgrind is useful for many things, not just memory leaks, and reducing the
noise level is benefical. I don't do this as a matter of course, blindly.
I'm aware of the Debian OpenSSL accident; indeed I had earlier and
coincidentally made the opposite choice in my company's tree because I
carefully reviewed the code in question and judged (a) there was no cheap,
correct fix and (b) the cost of a correct fix was more than the cost of the
noise.

From: sfuerst on
On Mar 11, 3:54 am, Urs Thuermann <u...(a)isnogud.escape.de> wrote:
> I have a typical producer/consumer problem (with varying-sized
> elements) which I currently have solved using a ring buffer in the
> classical way.  Because of the varying size, no prior safe way to know
> the needed buffer size and to reduce copying I want to change that to
> linked list of items passed from producer to consumer.
>
> The producer would allocate memory for an item and append it to the
> list, the consumer would dequeue from the beginning of the list and
> free the memory for that item afterwards.
>
> The average rate will be roughly 40 to 50 allocations/deallocations in
> a strict LIFO order and there will be 30000 to 60000 items on the list.
>
> My questions are:
>
> 1. Will typical implementations of malloc()/free() in libc handle this
>    load well?  Or should I implement my own memory management?  I
>    currently use glibc on Debian but would also like to know about
>    other libc implementations.  BTW, the hardware is an Intel x86 CPU
>    at about 2GHz.

The easiest response is to say "Benchmark it and see". However, I
think I can guess what you will find.

The glibc allocator is very fast. However, it has a problem with the
producer-consumer pattern. It uses "arenas" with separate locking.
When allocating, it tries to lock the last used arena, and if that
fails, uses a different one. When freeing, it needs to lock the arena
of that chunk of memory. The net result is that threads can end up
owning arenas with vastly different sizes, which can look like a
memory leak. This behaviour is described at
http://google-perftools.googlecode.com/svn/trunk/doc/tcmalloc.html

So google wrote their own allocator (tcmalloc) to fix this problem.
You may want to give it a try as it performs fairly well. The only
problem with it is that it doesn't return memory to the operating
system. This may pose a problem for long-running server tasks.

Another allocator you might want to try is Hoard. That one is fairly
old, and faster than tcmalloc for small allocation sizes. For large
sizes though, it is catastrophically slower, so you may want to
benchmark it to see if it helps you or not.

Then there is the allocator I wrote... which is faster than all three
of the above. It unfortunately, isn't open source. I have a nice
page showing the results of googles "t-test1" memory allocator
benchmark for various numbers of threads and average allocation sizes
for the above allocators. It should help you decide which one may be
the best for you: http://locklessinc.com/benchmarks.shtml

A final option is to write your own allocator. This is probably only
worth doing if the size-range of things on your queue is small. Then
you may use a simple linked list of free nodes, which should be fast
enough for you. If you have a wide size-range, then you should know
that writing a general purpose allocator isn't a 200 line job. To get
something better than the above allocators will probably require a few
thousand lines or more.

> 2. Are malloc() and free() thread-safe (according to POSIX and/or in
>    typical libc implementations) or do I have to use a mutex?
>
> urs

They are in glibc. In older libcs, not necessarily. I'd also be
careful of some of the lesser-known allocators. Most of the ones I've
tested seemed to be fairly buggy if you have multiple threads
executing allocations and frees simultaneously. i.e. if something
crashes with the t-test1 benchmark, then it definitely isn't worth
using. This is the main reason that I don't have more allocators on
the benchmark page. Most simply didn't work properly when tested
hard.

The only other one of mention is jemalloc, which is the allocator that
firefox uses. That allocator is designed for minimal memory usage
rather than allocation speed. The result is that it is quite slow. I
thought that it would be unfair to compare it to the speed-demon
allocators which didn't care quite so much for memory usage.

Steven
From: Ersek, Laszlo on
In article <nci077-sg2.ln1(a)wilbur.25thandClement.com>,
William Ahern <william(a)wilbur.25thandClement.com> writes:

> [...] Often a
> programmer makes the "premature optimization", especially in corner cases
> such as unwinding intermediate commitments after an error, that it's okay to
> lose track of an object because the allocator keeps a reference, neglecting
> altogether the possible security ramifications or the side-effect of losing
> possible signals on code correctness.

I couldn't tell how often this happens in "the real world", but it seems
very plausible. In the end, some of said allocators should probably be
written in the first place so that the tedium of such deep unwindings on
error paths can be avoided. Thanks for bringing this up.

lacos
From: Rainer Weikusat on
sfuerst <svfuerst(a)gmail.com> writes:

[...]

> If you have a wide size-range, then you should know that writing a
> general purpose allocator isn't a 200 line job. To get something
> better than the above allocators will probably require a few
> thousand lines or more.

This should have been "implementing the malloc interface such that the
implementation performs reasonably well for the usual benchmark
cases isn't a 200 line job". Although it actually even might be: The
Linux-kernel uses a power-of-two-freelist allocator based on the
object caching allocator it also uses. And 'if it is good enough for
Linux, it will be good enough for $my_application' is usually a
sensible proposition. But it is really much better to not try to do
this to begin with. And for this case, a 'general purpose allocator'
which basically avoids external fragmentation and whose allocation and
deallocation operations are guaranteed to be fast and complete in
constant time can be implemented in less than 200 lines of code[*],
provided that scalability regarding concurrent allocations/
deallocations from multiple threads is not a priority concern.

But 'general purpose memory allocation' is actually a complicated
approach to problems which can often be solved in a much simpler
way[**]: Nowadays, computers don't have small amounts of RAM anymore
and reusing an area of already allocated space for different purposes
within a single program isn't really necessary. For instance, another
approach is to get a 'large' block of memory from the kernel and use
that to satisfy allocation requests for 'small structures' from many
subsystems which all cache their allocated structures internally and
reuse them before allocating new structures. Especially in a program
which isn't supposed to ever stop, every situation which did occur in
the past will happen again in future and this implies that letting go
of memory which has been allocated for a specific purpose just means
'running the risk that it won't be available next time'.

[*] Known to be good enough for the needs of a content-filtering
policy enforcing HTTP proxy serving up to 40 concurrent users while
running on a 200Mhz ARM-based appliance w/ 64M of RAM.

[**] Ever wondered why only software is always suposed to be 'general
purpose' while all other kinds of devices are usually intended to
be good at fulfilling a specific purpose? Assuming that 'general
purpose' is something to always strive for, why are there no general
purpose devices combining the functionality of a coffee maker, a
lawnmover, a washing machine, a mixer, a sports car and a truck in
one? Could it be that the idea is really just completely braindead?