D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 3284 - snn linked programs never release memory back to the OS
Summary: snn linked programs never release memory back to the OS
Status: RESOLVED INVALID
Alias: None
Product: D
Classification: Unclassified
Component: druntime (show other issues)
Version: D2
Hardware: All Windows
: P1 blocker
Assignee: No Owner
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2009-09-03 03:08 UTC by Dominik Susmel
Modified: 2023-01-19 03:33 UTC (History)
4 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Dominik Susmel 2009-09-03 03:08:12 UTC
Programs using GC or manual malloc/free never actually release memory back to the OS (Windows), which causes programs with heavy memory allocation / deallocation to utilize page swaps heavily and eventually crash.

This is a blocking issue persistent in 1.047 release also (it was not available in version selection).

You can trivially reproduce this effect by malloc'ing several MB's and subsequently freeing them - watch the process memory usage before and after it.
Comment 1 Vladimir Panteleev 2009-12-03 19:50:59 UTC
Actually, the garbage collector doesn't use malloc to allocate heap memory (it uses the OS-specific page allocation functions), but it does suffer from the same problem.
Comment 2 Cauterite 2016-08-20 00:39:13 UTC
I highly suspect this issue has already been resolved.
Here's a simple test:

import core.memory;
void main() {
	// allocate and free lots of 10MB arrays
	foreach (_; 0 .. 1000) {
		auto x = GC.calloc(10_000_000, 1);
		GC.free(x);
		x = null;
	};

	import std.c.stdio;
	printf("done\n"); getchar();
};

if you remove the `GC.free(x)` the working set will grow to >1GB. if you leave it in, memory usage is normal ~15MB or so.
so the GC is definitely releasing pages back to the OS when it deallocates.

And before you ask, yes I am linking with SNN.lib
Comment 3 Cauterite 2016-08-21 20:07:31 UTC
reopen if there's still a way to trigger this bug
Comment 4 anonymous4 2016-08-22 10:20:39 UTC
As I understand, the test is as follows:

import core.memory, core.stdc.stdio;
void main()
{
	void*[100] arrays;

	// allocate and free lots of 10MB arrays
	foreach (ref x; arrays)
	{
		x = GC.calloc(10_000_000, 1);
	}

	foreach (ref x; arrays)
	{
		GC.free(x);
		x = null;
	}

	puts("must have a small working set here");
	getchar();
}

(didn't test)
i.e. the working set never shrinks, so your best strategy is not let it ever grow.
Comment 5 Cauterite 2016-08-22 10:56:03 UTC
My mistake; your adjusted test does in fact leave a massive working set.
I think I misunderstood the original bug report, because when you call GC.minimize() it does successfully reduce working set to normal size.

So the exact problem then is that the GC doesn't call minimize() automatically when it is appropriate. Currently, minimize() is only ever called when an allocation fails.

Ideally the GC should minimize during collection whenever the amount of unused reserved memory reaches some threshold. With my limited knowledge of the GC's internals this sounds like a simple patch, so I might give it a crack soon. Lest this bug remain open for 7 whole years.
Comment 6 anonymous4 2016-08-22 12:42:02 UTC
The original description probably complains about C malloc too - worth checking.
Comment 7 Ate Eskola 2021-11-01 17:53:08 UTC
The function in question reside in the core namespace, so reclassifying as a DRuntime issue.
Comment 8 Walter Bright 2023-01-19 03:11:38 UTC
snn.lib uses the Win32 HeapAlloc and HeapFree routines for malloc/free:

https://github.com/DigitalMars/dmc/blob/master/src/HEAP32/malloc.c#L22
Comment 9 Vladimir Panteleev 2023-01-19 03:33:13 UTC
I think this needs more focus/clarity of what is broken and needs to be fixed. Is it the C functions or the GC?

Note that we don't use the libc allocators in the GC, we use the OS APIs directly.

Also worth noting that heap allocators, whether new (GC), malloc (libc), or HeapAlloc (OS), are all vulnerable to fragmentation. Programs can only release memory back to the OS if the entire page is free.

It's possible that we no longer release memory to the OS after a GC cycle, because in many applications any released memory is going to be immediately requested again. Applications which require memory in bursts are comparatively rare. I recall that we no longer reserve memory from the OS - though it was a thing we could do and it aligned with the GC design, it was not useful in any measurable way, so it was removed.