digitalmars.D - Problems with GC, trees and array concatenation
- Babele Dunnit (58/58) Jun 01 2007 Hello everybody,
- Kirk McDonald (14/41) Jun 01 2007 It is worth reviewing what this line of code does:
- Oskar Linde (21/44) Jun 01 2007 I tried both with GDC 0.23 on OSX, and neither of them leaked any
- BCS (3/8) Jun 01 2007 Would not real[] allocations be tagged as "does not contain pointers"?
- Oskar Linde (6/15) Jun 01 2007 Yes, but I don't see how that would make any difference. There will, as
- Regan Heath (5/7) Jun 01 2007 In D1.001 a new type aware GC was implemented:
- Paolo Invernizzi (9/26) Jun 03 2007 I don't understand. Where are the spurious pointers coming in the above
- Oskar Linde (17/33) Jun 03 2007 Stack data (and registers) for instance. Possibly other sections of data...
- Lutger (5/9) Jun 03 2007 I can confirm this leak on windows XP with latest DMD. It just keeps
- Paolo Invernizzi (16/51) Jun 04 2007 Again, I don't understand. The only library imported in the example is
- Babele Dunnit (10/20) Jun 04 2007 That is good theory, but this means that this version of DMD is unuseful...
- Frits van Bommel (18/43) Jun 04 2007 Looking at the GC code I can't seem to find any place where arr[length
- Babele Dunnit (12/29) Jun 04 2007 Woagh!
- Oskar Linde (26/36) Jun 04 2007 The code that clears arr[length.._gc.cap(arr)] lies in gcx.d. Search for...
- David B. Held (3/24) Jun 04 2007 Could one of you guys file a bug report for this? Looks pretty importan...
- Babele Dunnit (7/31) Jun 04 2007 My test case seems to run rock-steady now under Windows. BTW, much faste...
- Babele Dunnit (4/6) Jun 05 2007 This question is still open...
- Babele Dunnit (7/7) Jun 06 2007 Hi all,
Hello everybody, I had a HELL of a day to understand what was wrong and to strip it out from my sources, but now I got it. Run this and keep an eye on the Task Manager: your memory will be eaten until death. Please note that: 1) the keys of it all seems to be the "recursive" Individual class (I was playing with trees). Substitute the class Individual { Individual[20] children; } with a class Individual { real[20] values; } and the GC will work flawlessly. 2) please note that I am using static arrays, but dynamic arrays behave in the same way. 3) If you use array slicing and direct assignments everything is OK. Please try both "loseMemory" and "everythingOk" versions. 4) no problem if you DON'T assign arrays (i.e, neither version is selected). GC works OK. 5) I am playing under Windows XP, DMD version 1.014. here he cometh da snippet: ---------------------------------------------------------------------------------------------------------- import std.stdio; class Individual { Individual[20] children; } class Population { void grow() { foreach(inout individual; individuals) { individual = new Individual; } } Individual[20000] individuals; } version = loseMemory; int main(char[][] args) { Population testPop1 = new Population; Population testPop2 = new Population; Individual[40000] indi; for(int i = 0; i < 1000000; i++) { writefln("Round %d", i); testPop1.grow(); testPop2.grow(); version (loseMemory){ indi[] = testPop1.individuals ~ testPop2.individuals; } version (everythingOk){ indi[0..20000] = testPop1.individuals; indi[20000..40000] = testPop2.individuals;} } return 0; } ------------------------------------------------------------------------------------------------------------------- Ciao, Bab
Jun 01 2007
Babele Dunnit wrote:int main(char[][] args) { Population testPop1 = new Population; Population testPop2 = new Population; Individual[40000] indi; for(int i = 0; i < 1000000; i++) { writefln("Round %d", i); testPop1.grow(); testPop2.grow(); version (loseMemory){ indi[] = testPop1.individuals ~ testPop2.individuals; } version (everythingOk){ indi[0..20000] = testPop1.individuals; indi[20000..40000] = testPop2.individuals; } } return 0; }It is worth reviewing what this line of code does: indi[] = testPop1.individuals ~ testPop2.individuals; When you concatenate two static arrays, the result is a dynamic array. In other words, that concatenation allocates a new dynamic array of 40,000 elements on the heap, copies the elements of the two sub-arrays into it. This array is then copied /again/ into the static array 'indi'. The 'Ok' version avoids both the allocation (which is causing the GC to eat all that memory) and the double-copying. -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Jun 01 2007
Babele Dunnit wrote:Hello everybody, I had a HELL of a day to understand what was wrong and to strip it out from my sources, but now I got it. Run this and keep an eye on the Task Manager: your memory will be eaten until death. Please note that: 1) the keys of it all seems to be the "recursive" Individual class (I was playing with trees). Substitute the class Individual { Individual[20] children; } with a class Individual { real[20] values; } and the GC will work flawlessly. 2) please note that I am using static arrays, but dynamic arrays behave in the same way. 3) If you use array slicing and direct assignments everything is OK. Please try both "loseMemory" and "everythingOk" versions.I tried both with GDC 0.23 on OSX, and neither of them leaked any memory, so I can only speculate about what is causing this. The D GC is not precise (although it has become much better since 1.001). There will almost always be some spurious pointers. Allocating large arrays (such as what happens behind the scenes when doing array concatenation) will always carry a risk of allocated memory being hit by a spurious pointer. The risk of your application having runaway or contained memory leaks depend on the allocation patterns you have, but in general, the larger your objects (like arrays) are, the larger the chance that you need to handle their memory manually. The problem is increased if you have many objects containing mixed pointer and non-pointer, but apparantly random data. So in conclusion, I am quite sure the following line: indi[] = testPop1.individuals ~ testPop2.individuals; Is the cause of the memory leak, because it allocates a huge chunk of memory that is left to the GC to free. The chance of those huge chunks being hit by a spurious pointer seems quite high. I have no idea why the Individual->real substitution above would make any difference, if not by pure chance. /Oskar
Jun 01 2007
Reply to Oskar,I have no idea why the Individual->real substitution above would make any difference, if not by pure chance.Would not real[] allocations be tagged as "does not contain pointers"?/Oskar
Jun 01 2007
BCS skrev:Reply to Oskar,Yes, but I don't see how that would make any difference. There will, as far as I can tell, never be any dangling pointers or otherwise spurious pointers generated by the Individual class, unless the array concatenation code somehow generates non-zero-initialized data. /OskarI have no idea why the Individual->real substitution above would make any difference, if not by pure chance.Would not real[] allocations be tagged as "does not contain pointers"?
Jun 01 2007
Oskar Linde Wrote:I have no idea why the Individual->real substitution above would make any difference, if not by pure chance.In D1.001 a new type aware GC was implemented: http://www.digitalmars.com/d/changelog.html#new1_001 Is it possible that this is why substituting 'Individual' for 'real' makes a difference. When you use 'real' your memory consists of mainly 'real's which are non-pointer types, greatly reducing the risk of spurious pointer collisions. Regan Heath
Jun 01 2007
Oskar Linde wrote:There will almost always be some spurious pointers. Allocating large arrays (such as what happens behind the scenes when doing array concatenation) will always carry a risk of allocated memory being hit by a spurious pointer.I don't understand. Where are the spurious pointers coming in the above example?The risk of your application having runaway or contained memory leaks depend on the allocation patterns you have, but in general, the larger your objects (like arrays) are, the larger the chance that you need to handle their memory manually. The problem is increased if you have many objects containing mixed pointer and non-pointer, but apparantly random data.Again, I don't understand. I don't see any random data.So in conclusion, I am quite sure the following line: indi[] = testPop1.individuals ~ testPop2.individuals; Is the cause of the memory leak, because it allocates a huge chunk of memory that is left to the GC to free. The chance of those huge chunks being hit by a spurious pointer seems quite high.This is a nonsense. What's the meaning of having a garbage collector if, huge or not huge, it cannot free a block of memory is a so pretty linear example? For sure, I'm missing something... Walter? Cheers, Paolo.
Jun 03 2007
Paolo Invernizzi skrev:Oskar Linde wrote:Stack data (and registers) for instance. Possibly other sections of data from the runtime or libraries.There will almost always be some spurious pointers. Allocating large arrays (such as what happens behind the scenes when doing array concatenation) will always carry a risk of allocated memory being hit by a spurious pointer.I don't understand. Where are the spurious pointers coming in the above example?The GC is not guaranteed to free all blocks of memory, but the usage patterns in most applications make the amount of noncollectable memory bound by a constant factor. I was not able to reproduce the unbounded memory growth in the original example, and if it is really there, it seems to me it may very likely be a bug, as the program, as you say, never should produce any considerable amount of "random" data. Unbounded growth should normally only appear if the noncollected memory blocks themselves contain spurious pointers. I am not too familiar with the D GC and allocator, but as far as I know, care is taken to make sure no uninitialized (random) data is introduced to the GC. I'd be interested to hear on what platform the original sample gives unbounded memory leakage. /OskarIs the cause of the memory leak, because it allocates a huge chunk of memory that is left to the GC to free. The chance of those huge chunks being hit by a spurious pointer seems quite high.This is a nonsense. What's the meaning of having a garbage collector if, huge or not huge, it cannot free a block of memory is a so pretty linear example? For sure, I'm missing something...
Jun 03 2007
Oskar Linde wrote: ...I'd be interested to hear on what platform the original sample gives unbounded memory leakage. /OskarI can confirm this leak on windows XP with latest DMD. It just keeps growing until it runs out of memory. Not so if used with array of reals, or when you tell the gc that Individual hasNoPointers.
Jun 03 2007
Oskar Linde wrote:Paolo Invernizzi skrev:Again, I don't understand. The only library imported in the example is the std.stdio, but I guess that the leak are not coming from that (Babele, can you confirm? I'm on OS X...). And, as I don't see how the above program can put on the stack something similar to a pointer (everything it's null, or IT'S a pointer), it remains only the runtime... *sigh* Are really the registers to be considered as a possible font of leaks?Oskar Linde wrote:Stack data (and registers) for instance. Possibly other sections of data from the runtime or libraries.There will almost always be some spurious pointers. Allocating large arrays (such as what happens behind the scenes when doing array concatenation) will always carry a risk of allocated memory being hit by a spurious pointer.I don't understand. Where are the spurious pointers coming in the above example?I simply cannot see how the usage pattern above can produce leak. Must we avoid dynamic array concatenation?The GC is not guaranteed to free all blocks of memory, but the usage patterns in most applications make the amount of noncollectable memory bound by a constant factor.Is the cause of the memory leak, because it allocates a huge chunk of memory that is left to the GC to free. The chance of those huge chunks being hit by a spurious pointer seems quite high.This is a nonsense. What's the meaning of having a garbage collector if, huge or not huge, it cannot free a block of memory is a so pretty linear example? For sure, I'm missing something...I was not able to reproduce the unbounded memory growth in the original example, and if it is really there, it seems to me it may very likely be a bug, as the program, as you say, never should produce any considerable amount of "random" data. Unbounded growth should normally only appear if the noncollected memory blocks themselves contain spurious pointers. I am not too familiar with the D GC and allocator, but as far as I know, care is taken to make sure no uninitialized (random) data is introduced to the GC.Yep, that's the point! I can understand it's better to keep under control big chunks of data full of random values, but it seems to me that the fact nobody can understand in an easy way that Babele example WILL leaks it's a little frightening.I'd be interested to hear on what platform the original sample gives unbounded memory leakage.DMD 1.014 on XP, I guess.. Cheers, Paolo
Jun 04 2007
Oskar Linde Wrote:The GC is not guaranteed to free all blocks of memory, but the usage patterns in most applications make the amount of noncollectable memory bound by a constant factor.That is good theory, but this means that this version of DMD is unuseful for production software. HOW MUCH is that upper bound? Will it be enough to have 2 GB of RAM? or 4 GB? I am running a genetic algorithm, which ideally should run "forever"... nonetheless, I wrote some Java SW (which I basically dont like) which is running server-side since last 3 years... OK, OK, we all know how Java works, and the Java GC is more mature, and Sun has a lot of money, blah blah.. Thinking at it again, a 10-children individual and two 200-individual population will amount to 1.480KB when properly garbage collected, but will raise to a (relatively) impressive 31MB when using array concatenation (I stopped it after 700.000 iterations). Try It Yourself. And it seems to me that these are not *big* arrays. And all pointers are null. And GC works OK when you do *NOT* use concatenation (even on "real" trees, I can assure you... I got some 300-nodes, 20-level-deep trees in my program here, and when not using concatenation everything gets collected smoothly) So, I should say, there is a bug in ARRAYS CONCATENATION, not in GARBAGE COLLECTION... Array concatenation introduces something in memory layout so that, later, the garbage collection cannot reclaim that memory zone...I'd be interested to hear on what platform the original sample gives unbounded memory leakage.Windows XP, DMD 1.014 (point 5 of my original post) From another post:So in conclusion, I am quite sure the following line: indi[] = testPop1.individuals ~ testPop2.individuals; Is the cause of the memory leak, because it allocates a huge chunk of memory that is left to the GC to free. The chance of those huge chunks being hit by a spurious pointer seems quite high.again: if you DO NOT concatenate those two arrays, everything is properly garbage collected. So, why on earth the GC should be able to collect the "original" arrays and not the "copied" ones?? And it happens also with very small vectors (see above), and still I do not see from where those spurious pointer should come from (holy smoke, Walter was so kind to make D initialize everything...) Seems to me that DEFINITELY there is something in array concatenation code which fools the GC - the Windows version of it, I mean... Ciao, Babele
Jun 04 2007
Babele Dunnit wrote:Oskar Linde Wrote:[snip]The GC is not guaranteed to free all blocks of memory, but the usage patterns in most applications make the amount of noncollectable memory bound by a constant factor.So, I should say, there is a bug in ARRAYS CONCATENATION, not in GARBAGE COLLECTION... Array concatenation introduces something in memory layout so that, later, the garbage collection cannot reclaim that memory zone...Looking at the GC code I can't seem to find any place where arr[length .. _gc.cap(arr)] (the unused part of the array allocation) is initialized. This could explain the issue if your arrays have different lengths (since the data from an longer old array may be present after a shorter new array, and is considered as "live" pointers by the GC because it's within the same allocation block). However, this seems to be the case for straight allocation as well, not just concatenation. If this is the cause, you probably have the same issue if you replace indi[] = testPop1.individuals ~ testPop2.individuals; by auto tmp = new Individual[](testPop1.length + testPop2.length); tmp[0 .. testPop1.length] = testPop1; tmp[testPop1.length .. $] = testPop2; indi[] = tmp; Is this the case?I'd be interested to hear on what platform the original sample gives unbounded memory leakage.Windows XP, DMD 1.014 (point 5 of my original post) From another post:So in conclusion, I am quite sure the following line: indi[] = testPop1.individuals ~ testPop2.individuals; Is the cause of the memory leak, because it allocates a huge chunk of memory that is left to the GC to free. The chance of those huge chunks being hit by a spurious pointer seems quite high.again: if you DO NOT concatenate those two arrays, everything is properly garbage collected. So, why on earth the GC should be able to collect the "original" arrays and not the "copied" ones?? And it happens also with very small vectors (see above), and still I do not see from where those spurious pointer should come from (holy smoke, Walter was so kind to make D initialize everything...) Seems to me that DEFINITELY there is something in array concatenation code which fools the GC - the Windows version of it, I mean...
Jun 04 2007
Frits van Bommel Wrote:Looking at the GC code I can't seem to find any place where arr[length .. _gc.cap(arr)] (the unused part of the array allocation) is initialized. This could explain the issue if your arrays have different lengths (since the data from an longer old array may be present after a shorter new array, and is considered as "live" pointers by the GC because it's within the same allocation block). However, this seems to be the case for straight allocation as well, not just concatenation. If this is the cause, you probably have the same issue if you replace indi[] = testPop1.individuals ~ testPop2.individuals; by auto tmp = new Individual[](testPop1.length + testPop2.length); tmp[0 .. testPop1.length] = testPop1; tmp[testPop1.length .. $] = testPop2; indi[] = tmp; Is this the case?Woagh! You got it, man! Actually, the Population is not an array itself but a container, so the correct code is: auto tmp = new Individual[](testPop1.individuals.length + testPop2.individuals.length); tmp[0 .. testPop1.individuals.length] = testPop1.individuals; tmp[testPop1.individuals.length .. $] = testPop2.individuals; indi[] = tmp; but the leak is there! I still do not understand one thing, though: you say "if your arrays have different lengths", but my arrays are fixed in size... they are even static! Shouldn't GC try to allocate and recycle always the same fixed-size chunk of memory? and why the leak does not shows up under OsX?? anyway, thx! Babele
Jun 04 2007
Frits van Bommel skrev:Babele Dunnit wrote:The code that clears arr[length.._gc.cap(arr)] lies in gcx.d. Search for the phrase "inline" The code is actually commented out on DMD: //foreach(inout byte b; cast(byte[])(p + size)[0..binsize[bin] - size]) { b = 0; } A patch that reverses that comment: --- gcx.d 2007-06-04 16:47:02.354590379 +0200 +++ gcx.d.new 2007-06-04 16:46:53.331933006 +0200 -297,7 +297,7 gcx.bucket[bin] = (cast(List *)p).next; //memset(p + size, 0, binsize[bin] - size); // 'inline' memset - Dave Fladebo. - //foreach(inout byte b; cast(byte[])(p + size)[0..binsize[bin] - size]) { b = 0; } + foreach(inout byte b; cast(byte[])(p + size)[0..binsize[bin] - size]) { b = 0; } //debug(PRINTF) printf("\tmalloc => %x\n", p); debug (MEMSTOMP) memset(p, 0xF0, size); } Actually seems to remove the memory leak on Linux 1.014... I am unable to test this on windows. Note that on GDC, the above code is replaced by a memset(...,0,...) that is run conditional to a version(GNU), so that may be the reason the leak doesn't exist on OSX. /OskarSeems to me that DEFINITELY there is something in array concatenation code which fools the GC - the Windows version of it, I mean...Looking at the GC code I can't seem to find any place where arr[length .. _gc.cap(arr)] (the unused part of the array allocation) is initialized. This could explain the issue if your arrays have different lengths (since the data from an longer old array may be present after a shorter new array, and is considered as "live" pointers by the GC because it's within the same allocation block).
Jun 04 2007
Oskar Linde wrote:[...] The code is actually commented out on DMD: //foreach(inout byte b; cast(byte[])(p + size)[0..binsize[bin] - size]) { b = 0; } A patch that reverses that comment: --- gcx.d 2007-06-04 16:47:02.354590379 +0200 +++ gcx.d.new 2007-06-04 16:46:53.331933006 +0200 -297,7 +297,7 gcx.bucket[bin] = (cast(List *)p).next; //memset(p + size, 0, binsize[bin] - size); // 'inline' memset - Dave Fladebo. - //foreach(inout byte b; cast(byte[])(p + size)[0..binsize[bin] - size]) { b = 0; } + foreach(inout byte b; cast(byte[])(p + size)[0..binsize[bin] - size]) { b = 0; } //debug(PRINTF) printf("\tmalloc => %x\n", p); debug (MEMSTOMP) memset(p, 0xF0, size); } [...]Could one of you guys file a bug report for this? Looks pretty important. Dave
Jun 04 2007
Oskar Linde Wrote:The code is actually commented out on DMD: //foreach(inout byte b; cast(byte[])(p + size)[0..binsize[bin] - size]) { b = 0; } A patch that reverses that comment: --- gcx.d 2007-06-04 16:47:02.354590379 +0200 +++ gcx.d.new 2007-06-04 16:46:53.331933006 +0200 -297,7 +297,7 gcx.bucket[bin] = (cast(List *)p).next; //memset(p + size, 0, binsize[bin] - size); // 'inline' memset - Dave Fladebo. - //foreach(inout byte b; cast(byte[])(p + size)[0..binsize[bin] - size]) { b = 0; } + foreach(inout byte b; cast(byte[])(p + size)[0..binsize[bin] - size]) { b = 0; } //debug(PRINTF) printf("\tmalloc => %x\n", p); debug (MEMSTOMP) memset(p, 0xF0, size); } Actually seems to remove the memory leak on Linux 1.014... I am unable to test this on windows.My test case seems to run rock-steady now under Windows. BTW, much faster (it does not reallocate!! :)) I'll let run the WHOLE BEAST tonight and tell you if it is still alive tomorrow... great! Grazie, Bab PS: Ok, but WHY is it commented out in actual Phobos distribution??
Jun 04 2007
Babele Dunnit Wrote:I'll let run the WHOLE BEAST tonight and tell you if it is still alive tomorrow...the BEAST died anyway, but for some other problem. I believe this patch is OK and proceed to file a bug reportPS: Ok, but WHY is it commented out in actual Phobos distribution??This question is still open... ciao
Jun 05 2007
Hi all, D 1.015, just released (thx Walter), fixes some GC problem, as in http://www.digitalmars.com/d/changelog.html#new1_015 but the array concatenation bug is still present (just tried!) and I proceeded to file it on Bugzilla (and patch it again in Phobos): http://d.puremagic.com/issues/show_bug.cgi?id=1258 ciao, Babele
Jun 06 2007