Jump to page: 1 2 3
Thread overview
Small @nogc experience report
Sep 07, 2018
Peter Alexander
Sep 07, 2018
Meta
Sep 07, 2018
Eugene Wissner
Sep 07, 2018
Meta
Sep 19, 2018
Walter Bright
Sep 08, 2018
Peter Alexander
Sep 08, 2018
Guillaume Piolat
Sep 08, 2018
Peter Alexander
Sep 10, 2018
Kagamin
Sep 10, 2018
rikki cattermole
Sep 11, 2018
Guillaume Piolat
Sep 11, 2018
Dukc
Sep 19, 2018
Shachar Shemesh
Sep 19, 2018
Shachar Shemesh
Sep 19, 2018
Walter Bright
Sep 19, 2018
Walter Bright
Sep 20, 2018
Shachar Shemesh
Sep 20, 2018
Atila Neves
Sep 20, 2018
Atila Neves
Sep 20, 2018
Seb
Sep 19, 2018
Per Nordlöw
Sep 07, 2018
H. S. Teoh
Sep 07, 2018
Eugene Wissner
Sep 19, 2018
Shachar Shemesh
September 07, 2018
I recently wrote a small program of ~600 lines of code to solve an optimisation puzzle. Profiling showed that GC allocations were using non-trivial CPU, so I decided to try and apply @nogc to remove allocations. This is a small experience report of my efforts.

1. My program does some initialisation before the main solver. I don't care about allocations in the initialisation. Since not all of my code needed to be @nogc, I couldn't add `@nogc:` to the top of the file and instead had to refactor my code into initialisation parts and main loop parts and wrap the latter in @nogc { ... }. This wasn't a major issue, but inconvenient.

2. For my code the errors were quite good. I was immediately able to see where GC allocations were occurring and fix them.

3. It was really frustrating that I had to make the compiler happy before I was able to run anything again. Due to point #1 I had to move code around to restructure things and wanted to make sure everything continued working before all GC allocations were removed.

4. I used std.algorithm.topNCopy, which is not @nogc. The error just says "cannot call non-@nogc function [...]". I know there are efforts to make Phobos more @nogc friendly, but seeing this error is like hitting a brick wall. I wouldn't expect topNCopy to use GC, but as a user, what do I do with the error? Having to dig into Phobos source is unpleasant. Should I file a bug? What if it is intentionally not @nogc for some subtle reason? Do I rewrite topNCopy?

5. Sometimes I wanted to add writeln to my code to debug things, but writeln is not @nogc, so I could not. I could have used printf in hindsight, but was too frustrated to continue.

6. In general, peppering my code with @nogc annotations was just unpleasant.

7. In the end I just gave up and used -vgc flag, which worked great. I had to ignore allocations from initialisation, but that was easy. It might be nice to have some sort of `ReportGC` RAII struct to scope when -vgc reports the GC.
September 07, 2018
On Friday, 7 September 2018 at 16:44:05 UTC, Peter Alexander wrote:
> I recently wrote a small program of ~600 lines of code to solve an optimisation puzzle. Profiling showed that GC allocations were using non-trivial CPU, so I decided to try and apply @nogc to remove allocations. This is a small experience report of my efforts.
>
> 1. My program does some initialisation before the main solver. I don't care about allocations in the initialisation. Since not all of my code needed to be @nogc, I couldn't add `@nogc:` to the top of the file and instead had to refactor my code into initialisation parts and main loop parts and wrap the latter in @nogc { ... }. This wasn't a major issue, but inconvenient.
>
> 2. For my code the errors were quite good. I was immediately able to see where GC allocations were occurring and fix them.
>
> 3. It was really frustrating that I had to make the compiler happy before I was able to run anything again. Due to point #1 I had to move code around to restructure things and wanted to make sure everything continued working before all GC allocations were removed.
>
> 4. I used std.algorithm.topNCopy, which is not @nogc. The error just says "cannot call non-@nogc function [...]". I know there are efforts to make Phobos more @nogc friendly, but seeing this error is like hitting a brick wall. I wouldn't expect topNCopy to use GC, but as a user, what do I do with the error? Having to dig into Phobos source is unpleasant. Should I file a bug? What if it is intentionally not @nogc for some subtle reason? Do I rewrite topNCopy?

Semi-unrelated, but I think you should open a bug for this one. I remember Andrei stating before that every function in std.algorithm except for LevehnsteinDistance(?) is @nogc, so he either missed topNCopy or the gc-ness of the function has changed sometime between ~2015 and now.

Actually, thanks to the fact that run.dlang.io provides the ability to compile a code snippet with all compilers since 2.060, this is very easy to debug:

import std.algorithm;
import std.range;

void main()
{
    int[100] store;
    auto nums = iota(100);
    nums.topNCopy(store[]); //compiles
}

Now if I add @nogc to main:

Up to      2.060  : Failure with output: onlineapp.d(4): Error: valid attribute identifiers are @property, @safe, @trusted, @system, @disable not @nogc

2.061   to 2.065.0: Failure with output: onlineapp.d(4): Error: undefined identifier nogc

2.066.0: Failure with output: onlineapp.d(8): Error: @nogc function 'D main' cannot call non-@nogc function 'std.algorithm.topNCopy!("a < b", Result, int[]).topNCopy'

2.067.1 to 2.078.1: Failure with output: onlineapp.d(8): Error: @nogc function 'D main' cannot call non-@nogc function 'std.algorithm.sorting.topNCopy!("a < b", Result, int[]).topNCopy'

Since      2.079.1: Failure with output: onlineapp.d(8): Error: `@nogc` function `D main` cannot call non-@nogc function `std.algorithm.sorting.topNCopy!("a < b", Result, int[]).topNCopy`

So it seems that it's never worked. Looking at the implementation, it uses a std.container.BinaryHeap, so it'd require a small rewrite to work with @nogc.

> 5. Sometimes I wanted to add writeln to my code to debug things, but writeln is not @nogc, so I could not. I could have used printf in hindsight, but was too frustrated to continue.

You are allowed to call "@gc" functions inside @nogc functions if you prefix them with a debug statement, e.g.:

void main() @nogc
{
    debug topNCopy(source, target);
}

You then have to pass the appropriate switch to the compiler to tell it to compile in the debug code.

> 6. In general, peppering my code with @nogc annotations was just unpleasant.
>
> 7. In the end I just gave up and used -vgc flag, which worked great. I had to ignore allocations from initialisation, but that was easy. It might be nice to have some sort of `ReportGC` RAII struct to scope when -vgc reports the GC.

I've been thinking lately that @nogc may have been going to far, and -vgc was all that was actually needed. -vgc gives you the freedom to remove or ignore GC allocations as necessary, instead of @nogc's all or nothing approach.
September 07, 2018
On Fri, Sep 07, 2018 at 04:44:05PM +0000, Peter Alexander via Digitalmars-d wrote:
> I recently wrote a small program of ~600 lines of code to solve an optimisation puzzle. Profiling showed that GC allocations were using non-trivial CPU, so I decided to try and apply @nogc to remove allocations.  This is a small experience report of my efforts.

Interesting report.  I'd say, if I had to do this, my first approach wouldn't be to use @nogc at all, but to use GC.disable() and scheduling my own calls to GC.collect() at the opportune time.  It depends on what your code is doing, obviously, but IME, the bulk of GC performance issues I've had in D was caused by the GC being overly eager to collect. By letting garbage accumulate for a little longer and invoking GC.collect at a lower frequency than it would normally would collect has resulted in significant performance boosts from 25% or so up to even 40% (but YMMV, of course).

This approach allows you to continue enjoying the convenience of having a GC while letting you tweak the GC collection frequency until you get good performance wins. After that, you can squeeze more performance out by identifying parts of the code where lots of garbage is generated, and changing the code to generate less garbage.  In a project of mine similar to yours, after I found a good low frequency schedule for collections, I found an allocation hotspot that was allocating lots of new arrays where existing ones could be reused.  Retouching just one or two places in that code to reuse a few arrays resulted in more performance gains.

tl;dr: IME, most GC performance issues can be alleviated by using GC.disable and scheduling GC.collect manually, and following the usual GC performance advice (reduce the amount of garbage your program generates, which translates to less need for collections and also less work per collection).  I haven't found much need to actually use @nogc (but YMMV, seems some people around here swear by @nogc).


T

-- 
"The whole problem with the world is that fools and fanatics are always so certain of themselves, but wiser people so full of doubts." -- Bertrand Russell. "How come he didn't put 'I think' at the end of it?" -- Anonymous
September 07, 2018
On Friday, 7 September 2018 at 17:01:09 UTC, Meta wrote:
> Semi-unrelated, but I think you should open a bug for this one. I remember Andrei stating before that every function in std.algorithm except for LevehnsteinDistance(?) is @nogc, so he either missed topNCopy or the gc-ness of the function has changed sometime between ~2015 and now.

It was never true. Here is another example:

import std.algorithm;

void main() @nogc
{
    int[4] a, b;

    fill(a[], b[]);
}

The funny thing is that fill() doesn't always allocate, but only if the element to fill with, is an array. fill() uses enforce() which allocates and throws. Other algorithms (e.g. equal) have special handling for character arrays and throw if they get wrong unicode or use some auto-decoding functions that aren't @nogc.
September 07, 2018
On Friday, 7 September 2018 at 16:44:05 UTC, Peter Alexander wrote:
> I recently wrote a small program of ~600 lines of code to solve an optimisation puzzle. Profiling showed that GC allocations were using non-trivial CPU, so I decided to try and apply @nogc to remove allocations. This is a small experience report of my efforts.
>
> 1. My program does some initialisation before the main solver. I don't care about allocations in the initialisation. Since not all of my code needed to be @nogc, I couldn't add `@nogc:` to the top of the file and instead had to refactor my code into initialisation parts and main loop parts and wrap the latter in @nogc { ... }. This wasn't a major issue, but inconvenient.
>
> 2. For my code the errors were quite good. I was immediately able to see where GC allocations were occurring and fix them.
>
> 3. It was really frustrating that I had to make the compiler happy before I was able to run anything again. Due to point #1 I had to move code around to restructure things and wanted to make sure everything continued working before all GC allocations were removed.
>
> 4. I used std.algorithm.topNCopy, which is not @nogc. The error just says "cannot call non-@nogc function [...]". I know there are efforts to make Phobos more @nogc friendly, but seeing this error is like hitting a brick wall. I wouldn't expect topNCopy to use GC, but as a user, what do I do with the error? Having to dig into Phobos source is unpleasant. Should I file a bug? What if it is intentionally not @nogc for some subtle reason? Do I rewrite topNCopy?
>
> 5. Sometimes I wanted to add writeln to my code to debug things, but writeln is not @nogc, so I could not. I could have used printf in hindsight, but was too frustrated to continue.
>
> 6. In general, peppering my code with @nogc annotations was just unpleasant.
>
> 7. In the end I just gave up and used -vgc flag, which worked great. I had to ignore allocations from initialisation, but that was easy. It might be nice to have some sort of `ReportGC` RAII struct to scope when -vgc reports the GC.

Phobos is a mine field for @nogc code. The worst thing is that some algorithms don't just allocate, but they allocate "sometimes". I always used "equal" without fear but one day I instantiated "equal" with some char slices and a simple comparison algorithm allocated. The same for fill(). Phobos is infected here and there with allocations, if you really want to write without GC, I wouldn't use Phobos at all.
September 07, 2018
On Friday, 7 September 2018 at 17:35:12 UTC, Eugene Wissner wrote:
> On Friday, 7 September 2018 at 17:01:09 UTC, Meta wrote:
>> Semi-unrelated, but I think you should open a bug for this one. I remember Andrei stating before that every function in std.algorithm except for LevehnsteinDistance(?) is @nogc, so he either missed topNCopy or the gc-ness of the function has changed sometime between ~2015 and now.
>
> It was never true. Here is another example:
>
> import std.algorithm;
>
> void main() @nogc
> {
>     int[4] a, b;
>
>     fill(a[], b[]);
> }
>
> The funny thing is that fill() doesn't always allocate, but only if the element to fill with, is an array. fill() uses enforce() which allocates and throws. Other algorithms (e.g. equal) have special handling for character arrays and throw if they get wrong unicode or use some auto-decoding functions that aren't @nogc.

I'm sure I've heard Andrei mention it multiple times, but I must be misremembering something.
September 08, 2018
On Friday, 7 September 2018 at 17:01:09 UTC, Meta wrote:
> You are allowed to call "@gc" functions inside @nogc functions if you prefix them with a debug statement, e.g.:

Thanks! I was aware that debug is an escape hatch for pure, but didn't consider it for @nogc.

> I've been thinking lately that @nogc may have been going to far, and -vgc was all that was actually needed. -vgc gives you the freedom to remove or ignore GC allocations as necessary, instead of @nogc's all or nothing approach.

I was thinking the same thing. The type system is a very heavy-weight and intrusive way to enforce something.

I'd love to know if anyone is making good use of @nogc in a larger code base and is happy with it. Weka.io?
September 08, 2018
On Saturday, 8 September 2018 at 08:07:07 UTC, Peter Alexander wrote:
> I'd love to know if anyone is making good use of @nogc in a larger code base and is happy with it. Weka.io?

Not Weka but we are happy with @nogc and without @nogc our job would be impossible.

You don't like it fine. But I can guarantee it has its uses.

There is no other choice when the runtime is disabled but to have @nogc.
It's a fantastic peace of mind for high-performance to be able to _enforce_ something will not allocate. If anything, that's superior to C++ when copying a std::vector will trigger an allocation etc.
September 08, 2018
On Saturday, 8 September 2018 at 08:32:58 UTC, Guillaume Piolat wrote:
> On Saturday, 8 September 2018 at 08:07:07 UTC, Peter Alexander wrote:
>> I'd love to know if anyone is making good use of @nogc in a larger code base and is happy with it. Weka.io?
>
> Not Weka but we are happy with @nogc and without @nogc our job would be impossible.
>
> You don't like it fine. But I can guarantee it has its uses.
>
> There is no other choice when the runtime is disabled but to have @nogc.
> It's a fantastic peace of mind for high-performance to be able to _enforce_ something will not allocate. If anything, that's superior to C++ when copying a std::vector will trigger an allocation etc.

Thanks for chiming in. That's good to know.
September 10, 2018
On Saturday, 8 September 2018 at 08:32:58 UTC, Guillaume Piolat wrote:
> There is no other choice when the runtime is disabled but to have @nogc.
> It's a fantastic peace of mind for high-performance to be able to _enforce_ something will not allocate.

You can't have a working GC allocation with disabled runtime, can you?
« First   ‹ Prev
1 2 3