Jump to page: 1 2 3
Thread overview
Minimize GC memory footprint
Jan 30, 2021
frame
Jan 30, 2021
Imperatorn
Jan 31, 2021
frame
Jan 31, 2021
Imperatorn
Feb 03, 2021
frame
Feb 05, 2021
Bastiaan Veelo
Feb 06, 2021
frame
Feb 06, 2021
frame
Feb 06, 2021
rikki cattermole
Feb 06, 2021
Imperatorn
Feb 06, 2021
Siemargl
Feb 06, 2021
rikki cattermole
Feb 06, 2021
frame
Feb 06, 2021
rikki cattermole
Feb 06, 2021
frame
Feb 06, 2021
Mike Parker
Feb 06, 2021
Siemargl
Feb 06, 2021
Siemargl
Feb 06, 2021
Mike Parker
Feb 06, 2021
frame
Feb 09, 2021
frame
Feb 13, 2021
Siemargl
Feb 13, 2021
frame
Feb 13, 2021
Siemargl
Jan 31, 2021
Max Haughton
January 30, 2021
Is there a way to force the GC to re-use memory in already existing pools?

I set maxPoolSize:1 to gain pools that can be quicker released after there no longer in use. This already reduces memory usage to 1:3. Sadly the application creates multiple pools that are not necessary in my POV - just fragmented temporary slice data like from format(). What can I do to optimize?
January 30, 2021
On Saturday, 30 January 2021 at 16:42:35 UTC, frame wrote:
> Is there a way to force the GC to re-use memory in already existing pools?
>
> I set maxPoolSize:1 to gain pools that can be quicker released after there no longer in use. This already reduces memory usage to 1:3. Sadly the application creates multiple pools that are not necessary in my POV - just fragmented temporary slice data like from format(). What can I do to optimize?

Do you want to optimize for reduced memory usage?
January 31, 2021
On Saturday, 30 January 2021 at 22:57:41 UTC, Imperatorn wrote:
> On Saturday, 30 January 2021 at 16:42:35 UTC, frame wrote:
>> Is there a way to force the GC to re-use memory in already existing pools?
>>
>> I set maxPoolSize:1 to gain pools that can be quicker released after there no longer in use. This already reduces memory usage to 1:3. Sadly the application creates multiple pools that are not necessary in my POV - just fragmented temporary slice data like from format(). What can I do to optimize?
>
> Do you want to optimize for reduced memory usage?

Yes, speed is secondary (long running daemon)
January 31, 2021
On Saturday, 30 January 2021 at 16:42:35 UTC, frame wrote:
> Is there a way to force the GC to re-use memory in already existing pools?
>
> I set maxPoolSize:1 to gain pools that can be quicker released after there no longer in use. This already reduces memory usage to 1:3. Sadly the application creates multiple pools that are not necessary in my POV - just fragmented temporary slice data like from format(). What can I do to optimize?

I can't tell you much about the inner workings of the GC, but maybe take a look at experimental.allocator and see if anything there can help you (specifically that you understand your program better than the GC)
January 31, 2021
On Sunday, 31 January 2021 at 04:12:14 UTC, frame wrote:
> On Saturday, 30 January 2021 at 22:57:41 UTC, Imperatorn wrote:
>> On Saturday, 30 January 2021 at 16:42:35 UTC, frame wrote:
>>> Is there a way to force the GC to re-use memory in already existing pools?
>>>
>>> I set maxPoolSize:1 to gain pools that can be quicker released after there no longer in use. This already reduces memory usage to 1:3. Sadly the application creates multiple pools that are not necessary in my POV - just fragmented temporary slice data like from format(). What can I do to optimize?
>>
>> Do you want to optimize for reduced memory usage?
>
> Yes, speed is secondary (long running daemon)

It says experimental, but it's fine:

https://dlang.org/phobos/std_experimental_allocator.html
February 03, 2021
On Sunday, 31 January 2021 at 12:14:53 UTC, Imperatorn wrote:

> It says experimental, but it's fine:
>
> https://dlang.org/phobos/std_experimental_allocator.html

Well, this looks very nice but I have to deal with GC as long I want to use other libraries that are relying on it or even just phobos.

Conclusion so far (for Windows):

32bit:
- GC just doesn't work at all

64bit:
- Collections are rare. It can be necessary to call GC.collect() manually.
- Scope guards to explicit clean up / free memory at function exit have no deep impact on most cases.
- If your application should save memory call GC.minimize() when it's appropriate.


It seems that calling GC.enable() if it's already enabled just disables the automatic GC again? Is this a bug? I cannot reproduce it outside my application yet since it's not clear when the GC starts collecting, but it always shows the same behaviour:

> // GC.enable();
Case A: The app is very kind in memory usage (~20 MB)

> GC.enable();
Case B: The app is consuming huge amount of memory (~900 MB)

> GC.disable();
> GC.enable();
Case A again

> GC.disable();
> GC.enable();
> GC.enable();
Case B again


I also have to struggle what the specs' text actually mean:

> This function is reentrant, and must be called once for every call to disable before automatic collections are enabled.

February 05, 2021
On Wednesday, 3 February 2021 at 13:37:42 UTC, frame wrote:
> I have to deal with GC as long I want to use other libraries that are relying on it or even just phobos.
>
> Conclusion so far (for Windows):
>
> 32bit:
> - GC just doesn't work at all

?? Do you mean no collections happen? 32bit GC should just work.

> 64bit:
> - Collections are rare. It can be necessary to call GC.collect() manually.
> - Scope guards to explicit clean up / free memory at function exit have no deep impact on most cases.
> - If your application should save memory call GC.minimize() when it's appropriate.
>
>
> It seems that calling GC.enable() if it's already enabled just disables the automatic GC again? Is this a bug? I cannot reproduce it outside my application yet since it's not clear when the GC starts collecting, but it always shows the same behaviour:
>
>> // GC.enable();
> Case A: The app is very kind in memory usage (~20 MB)
>
>> GC.enable();
> Case B: The app is consuming huge amount of memory (~900 MB)
>
>> GC.disable();
>> GC.enable();
> Case A again
>
>> GC.disable();
>> GC.enable();
>> GC.enable();
> Case B again

That looks like a bug indeed.

> I also have to struggle what the specs' text actually mean:
>
>> This function is reentrant, and must be called once for every call to disable before automatic collections are enabled.

I think it means that you need to make sure that enable() is called as many times as disable() is called before collection can happen automatically.

— Bastiaan.
February 06, 2021
On Friday, 5 February 2021 at 22:46:05 UTC, Bastiaan Veelo wrote:

> I think it means that you need to make sure that enable() is called as many times as disable() is called before collection can happen automatically.
>
> — Bastiaan.

Thanks, in the meanwhile I looked into the source:

> struct Gcx
> {
>    uint disabled; // turn off collections if >0
> ...
> }

> void enable()
> {
>    static void go(Gcx* gcx) nothrow
>    {
>        assert(gcx.disabled > 0);
>        gcx.disabled--;
>    }
>    runLocked!(go, otherTime, numOthers)(gcx);
> }

> void disable()
> {
>    static void go(Gcx* gcx) nothrow
>    {
>        gcx.disabled++;
>    }
>    runLocked!(go, otherTime, numOthers)(gcx);
> }

So that explains what's going on.

The assertion should kick in to warn about this issue. But it doesn't work on user code.
I assume the runtime is not compiled but just linked or do I need another argument switch?
February 06, 2021
On Friday, 5 February 2021 at 22:46:05 UTC, Bastiaan Veelo wrote:

> ?? Do you mean no collections happen? 32bit GC should just work.

No, it doesn't - this code fails on memory allocation and works fine with -m64 switch:


import std.stdio;
import core.memory : GC;

void main() {

    void usage() {
        writefln("Usage: %.2f MiB / collected: %d", (cast(double) GC.stats.usedSize) / 1_048_576, GC.profileStats.numCollections);
    }

    void foo() {
        string[] s;

        scope (exit) {
            s.length = 0;
        }

        foreach (i; 0 .. 50_000_00) {
            s ~= "a";
        }
    }

    foreach (i; 0 .. uint.max) {
        writefln("Round: %d", i + 1);
        foo();
        GC.collect();
        usage();
    }
}

...
Round: 24
Usage: 1603.57 MiB / collected: 27
Round: 25
Usage: 1691.64 MiB / collected: 28
Round: 26
Usage: 1729.50 MiB / collected: 29
Round: 27

core.exception.OutOfMemoryError@src\core\exception.d(647): Memory allocation failed
February 06, 2021
On 06/02/2021 3:32 PM, frame wrote:
> On Friday, 5 February 2021 at 22:46:05 UTC, Bastiaan Veelo wrote:
> 
>> ?? Do you mean no collections happen? 32bit GC should just work.
> 
> No, it doesn't - this code fails on memory allocation and works fine with -m64 switch:
> 
> 
> import std.stdio;
> import core.memory : GC;
> 
> void main() {
> 
>      void usage() {
>          writefln("Usage: %.2f MiB / collected: %d", (cast(double) GC.stats.usedSize) / 1_048_576, GC.profileStats.numCollections);
>      }
> 
>      void foo() {
>          string[] s;
> 
>          scope (exit) {
>              s.length = 0;

This won't do anything.

>          }
> 
>          foreach (i; 0 .. 50_000_00) {
>              s ~= "a";
>          }
>      }
> 
>      foreach (i; 0 .. uint.max) {
>          writefln("Round: %d", i + 1);

Don't forget to stdout.flush; Otherwise stuff can get caught in the buffer before erroring out.

>          foo();
>          GC.collect();
>          usage();
>      }
> }
> 
> ...
> Round: 24
> Usage: 1603.57 MiB / collected: 27
> Round: 25
> Usage: 1691.64 MiB / collected: 28
> Round: 26
> Usage: 1729.50 MiB / collected: 29
> Round: 27
> 
> core.exception.OutOfMemoryError@src\core\exception.d(647): Memory allocation failed

Turn on the precise GC, 32bit is a bit too small of a range and you can get false positives like in this case (at least looks like it).
« First   ‹ Prev
1 2 3