Memory leak with dynamic array
Steven Schveighoffer
schveiguy at yahoo.com
Tue Apr 13 05:56:03 PDT 2010
On Tue, 13 Apr 2010 08:30:57 -0400, Joseph Wakeling
<joseph.wakeling at webdrake.net> wrote:
> Joseph Wakeling wrote:
>> On the other hand, if the assumeSafeAppend takes place outside the loops
>> entirely, blow-up occurs -- because the command is not issued after the
>> array resize to zero? I've attached examples.
>
> If I include a line
>
> x.reserve(5_000_000);
>
> before the assumeSafeAppend, the memory does not explode indefinitely --
> but it uses about 3x as much memory as the 'NoLeak' code even when the
> latter lacks advance reservation of memory.
x.reserve(5_000_000) will reserve the memory for the first loop. But the
second time through the loop will start reallocating from scratch unless
you tell the runtime it's safe to reuse that memory.
Most likely it uses less memory than the noleak code because it has a nice
continuous 40MB region to use in subsequent loops. Because of the size of
the array, it will naturally gravitate towards that region, and once it
finds it, it will happily grow in place for a while.
assumeSafeAppend is needed to avoid reallocating the memory when setting
the length back to 0. It is an optimization designed specifically for
this purpose.
My recommended approach for your little code sample is this:
import std.array;
import std.stdio;
void main()
{
double[] x;
x.reserve(5_000_000); // heads up, runtime, I'm about to use 40MB of
continuous space
foreach(uint i;0..100) {
x.length = 0;
assumeSafeAppend(x); // you can reuse that memory I just got done
filling, I don't need it anymore.
foreach(uint j;0..5_000) {
foreach(uint k;0..1_000) {
x ~= j*k;
}
}
writefln("At iteration %u, x has %u elements.",i,x.length);
}
}
If you don't use reserve, here is what happens during the loop:
1st append: The smallest block possible to hold 1 double is found and
allocated
2nd append: If the block holding the first double can hold 2, the
"allocated" size of the block is increased to 2 doubles. Otherwise, the
smallest block possible to hold 2 doubles is found and allocated.
...
Which means the first loop will consume extra memory on its way to
building the full array. With reserve, the array is preallocated, so
every append is able to simply update the allocated length instead of
reallocating, increasing performance and saving memory.
Note that every time the reallocation occurs because the block isn't big
enough, the old block is left behind until the GC cleans it up. But even
when the GC cleans it up, it doesn't necessarily release it back to the
OS, so your program may still be consuming that memory.
-Steve
More information about the Digitalmars-d-learn
mailing list