www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Small nogc experience report

reply Peter Alexander <peter.alexander.au gmail.com> writes:
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 

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.
Sep 07 2018
next sibling parent reply Meta <jared771 gmail.com> writes:
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 

 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.
Sep 07 2018
next sibling parent reply Eugene Wissner <belka caraus.de> writes:
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.
Sep 07 2018
next sibling parent Meta <jared771 gmail.com> writes:
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.
Sep 07 2018
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 9/7/2018 10:35 AM, Eugene Wissner wrote:
 fill() uses enforce() which allocates and throws. 
The addition of -dip1008 stopped using the gc for throwing exceptions, but it's opt-in at the moment.
Sep 19 2018
prev sibling next sibling parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
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?
Sep 08 2018
next sibling parent reply Guillaume Piolat <spam spam.org> writes:
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.
Sep 08 2018
next sibling parent Peter Alexander <peter.alexander.au gmail.com> writes:
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.
Sep 08 2018
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
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?
Sep 10 2018
next sibling parent rikki cattermole <rikki cattermole.co.nz> writes:
On 10/09/2018 9:11 PM, Kagamin wrote:
 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?
Yes. GC.disable
Sep 10 2018
prev sibling parent Guillaume Piolat <spam smam.org> writes:
On Monday, 10 September 2018 at 09:11:38 UTC, Kagamin wrote:
 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?
Indeed and that's why you must ensure at compile-time that you have no `new` else you have a runtime crash, and one that might be difficult to test unless you have perfect coverage.
Sep 11 2018
prev sibling parent Dukc <ajieskola gmail.com> writes:
On Saturday, 8 September 2018 at 08:32:58 UTC, Guillaume Piolat 
wrote:
 Not Weka but we are happy with  nogc and without  nogc our job 
 would be impossible.
There is one way to code without garbage collector somewhat practically without annotating nogc, the way I use: Compile manually only those parts of runtime you need and have linker to link in only needed symbols (so there is no need to go stubbing the whole runtime). If you accidently allocate, you get linker errors. Still, nogc helps because it will tell right away where I called something I should not. I wonder if having an option to compile DRuntime without support for thearding would simplify portability. I once read the implementation and got the impression that theards are by far the most platform-dependant part of it.
Sep 11 2018
prev sibling parent reply Shachar Shemesh <shachar weka.io> writes:
On 08/09/18 11:07, 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?
No, sorry. Actually, yes. Well, sortof. The main Weka codebase hardly uses any annotations of any kind. Not nogc nor others. This is in the process of being amended, somewhat, but is not a high priority. We do use run-time detection of GC use. I.e. - we've modified the druntime to invoke a callback if a GC allocation takes place, and we then log that fact (with traceback). We then are able to search logs for GC allocations and remove them. So that's the "no" part. As pointed out, one of the main motivations for running Mecca was to clear up the strange solutions that have accumulated over the years. As such, the Mecca code does have nogc in much more wide use. So, yes. There is a catch, though. Writing Mecca with nogc required re-implementing quite a bit of druntime. Mecca uses its own exception allocations (mkEx, just saw it's not yet documented, it's under mecca.lib.exception). The same module also has "enforceNGC". We also have our own asserts. This is partially to support our internal logging facility, that needs a static list of formats, but it also solves a very important problem with D's nogc: void func() nogc { assert(condition, string); // string is useless without actual info about what went wrong. assert(condition, format(string, arg, arg)); // No good - format is not nogc ASSERT!"format"(condition, arg, arg); // nogc and convenient } So, yes, we do use nogc, but it took a *lot* of work to do it. The good news is that mecca is available, and you can just dub it into your project and use it, so you don't have to repeat that whole set of work. Mecca was advertised mostly around the reactor. While it is a piece of work I am very proud of, it is not the only part there, nor is it necessary to use mecca's reactor if you want just the library. In fact, nothing outside of mecca.reactor depends on the reactor running. Hope this helps, Shachar
Sep 19 2018
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/19/18 1:13 PM, Shachar Shemesh wrote:

 There is a catch, though. Writing Mecca with  nogc required 
 re-implementing quite a bit of druntime. Mecca uses its own exception 
 allocations (mkEx, just saw it's not yet documented, it's under 
 mecca.lib.exception). The same module also has "enforceNGC". We also 
 have our own asserts. This is partially to support our internal logging 
 facility, that needs a static list of formats, but it also solves a very 
 important problem with D's  nogc:
 
 void func()  nogc {
    assert(condition, string); // string is useless without actual info 
 about what went wrong.
    assert(condition, format(string, arg, arg)); // No good - format is 
 not  nogc
    ASSERT!"format"(condition, arg, arg); //  nogc and convenient
 }
 
 So, yes, we do use  nogc, but it took a *lot* of work to do it.
I'm running into this coincidentally right now, when trying to debug a PR. I found I'm getting a range error deep inside a phobos function. But because Phobos is trying to be pure nogc nothrow safe, I can do almost nothing to display what is wrong. What I ended up doing is making an extern(C) hook that had the "right" attributes, even though it's not nogc (let's face it, you are about to crash anyway). But it got me thinking, what a useless interface to display errors we have! Inside Throwable, there is the function toString(someDelegate sink) which prints out the exception trace. Near the front there is this: if (msg.length) { sink(": "); sink(msg); } My, wouldn't it be nice to be able to override this! And forget about the whole msg BS. When an exception trace is printed, there are almost no restrictions as to what can be done. We should delay the generation of the message until then as well! Not to mention that if we can output things piecemeal through the sink, we don't even have to allocate at all. I'm going to write up a more detailed post on this, but it's annoying to throw exceptions without any information EXCEPT what can be converted into a string at runtime at the time of exception. All that is missing is this hook to generate the message. -Steve
Sep 19 2018
next sibling parent Shachar Shemesh <shachar weka.io> writes:
On 19/09/18 21:35, Steven Schveighoffer wrote:
 On 9/19/18 1:13 PM, Shachar Shemesh wrote:
 
 There is a catch, though. Writing Mecca with  nogc required 
 re-implementing quite a bit of druntime. Mecca uses its own exception 
 allocations (mkEx, just saw it's not yet documented, it's under 
 mecca.lib.exception). The same module also has "enforceNGC". We also 
 have our own asserts. This is partially to support our internal 
 logging facility, that needs a static list of formats, but it also 
 solves a very important problem with D's  nogc:

 void func()  nogc {
    assert(condition, string); // string is useless without actual info 
 about what went wrong.
    assert(condition, format(string, arg, arg)); // No good - format is 
 not  nogc
    ASSERT!"format"(condition, arg, arg); //  nogc and convenient
 }

 So, yes, we do use  nogc, but it took a *lot* of work to do it.
I'm running into this coincidentally right now, when trying to debug a PR. I found I'm getting a range error deep inside a phobos function. But because Phobos is trying to be pure nogc nothrow safe, I can do almost nothing to display what is wrong. What I ended up doing is making an extern(C) hook that had the "right" attributes, even though it's not nogc (let's face it, you are about to crash anyway). But it got me thinking, what a useless interface to display errors we have! Inside Throwable, there is the function toString(someDelegate sink) which prints out the exception trace. Near the front there is this:         if (msg.length)         {             sink(": "); sink(msg);         } My, wouldn't it be nice to be able to override this! And forget about the whole msg BS. When an exception trace is printed, there are almost no restrictions as to what can be done. We should delay the generation of the message until then as well! Not to mention that if we can output things piecemeal through the sink, we don't even have to allocate at all. I'm going to write up a more detailed post on this, but it's annoying to throw exceptions without any information EXCEPT what can be converted into a string at runtime at the time of exception. All that is missing is this hook to generate the message. -Steve
Then by all means, have a look at ASSERT inside mecca.lib.exception.
Sep 19 2018
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 9/19/2018 11:35 AM, Steven Schveighoffer wrote:
 I'm running into this coincidentally right now, when trying to debug a PR. I 
 found I'm getting a range error deep inside a phobos function. But because 
 Phobos is trying to be pure  nogc nothrow  safe, I can do almost nothing to 
 display what is wrong.
That's why attribute enforcement is weakened in debug conditionals.
Sep 19 2018
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/19/2018 10:13 AM, Shachar Shemesh wrote:
    assert(condition, string); // string is useless without actual info about 
 what went wrong.
    assert(condition, format(string, arg, arg)); // No good - format is not
 nogc
Another method: debug assert(condition, format(string, arg, arg)); else assert(condition, string); because nogc is ignored in debug conditionals, just like purity is ignored in debug conditionals.
Sep 19 2018
parent reply Shachar Shemesh <shachar weka.io> writes:
On 19/09/18 22:53, Walter Bright wrote:
 On 9/19/2018 10:13 AM, Shachar Shemesh wrote:
    assert(condition, string); // string is useless without actual info 
 about what went wrong.
    assert(condition, format(string, arg, arg)); // No good - format is 
 not  nogc
Another method:   debug     assert(condition, format(string, arg, arg));   else     assert(condition, string); because nogc is ignored in debug conditionals, just like purity is ignored in debug conditionals.
That doesn't cut it on so many levels... First of all, no four lines solution that requires copy/paste (or worse, retyping) as a standard will actually get employed by programmers. The disincentive is too high. If we overcome this problem, we're still left with the fact that in all of the important runs the data I need in order to debug an assert violation is not going to be there when I need it. Let me put it this way: the Weka code base has over 13,000 asserts. Every single time an assert triggered on me that either did not have a message, or had only a message but not data, I needed more data than it had. It is, unfortunately, not true to say that this only ever happened in debug builds. In fact, we do release builds with asserts as part of our CI, so a relatively rare bug has a very high chance of throwing an assert in non-debug runs. Writing asserts should be easy, and making them useful in case they trigger should also be easy. What's more, soft mandating writing asserts with assert messages and parameters all but eliminates the common anti-pattern used by many novices of asserting that the compiler does what it's supposed to, e.g: if( a>13 ) return; assert(a<13); Shachar
Sep 19 2018
parent reply Atila Neves <atila.neves gmail.com> writes:
On Thursday, 20 September 2018 at 03:01:02 UTC, Shachar Shemesh 
wrote:
 On 19/09/18 22:53, Walter Bright wrote:
 On 9/19/2018 10:13 AM, Shachar Shemesh wrote:
    assert(condition, string); // string is useless without 
 actual info about what went wrong.
    assert(condition, format(string, arg, arg)); // No good - 
 format is not  nogc
Another method:   debug     assert(condition, format(string, arg, arg));   else     assert(condition, string); because nogc is ignored in debug conditionals, just like purity is ignored in debug conditionals.
That doesn't cut it on so many levels... First of all, no four lines solution that requires copy/paste (or worse, retyping) as a standard will actually get employed by programmers. The disincentive is too high.
This pattern is incredibly easy to wrap and reuse as needed. I would've done already if only I'd known nogc was ignored as well as pure.
Sep 20 2018
parent reply Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Thursday, 20 September 2018 at 10:50:49 UTC, Atila Neves wrote:
 This pattern is incredibly easy to wrap and reuse as needed. I 
 would've done already if only I'd known  nogc was ignored as 
 well as pure.
It's a recent development: https://dlang.org/changelog/2.082.0#debug-unsafe :
 Unsafe code can now be used in debug blocks
https://dlang.org/changelog/2.079.0 :
 Bugzilla 16492: support  nogc in debug{} blocks
Sep 20 2018
parent reply Atila Neves <atila.neves gmail.com> writes:
On Thursday, 20 September 2018 at 12:41:07 UTC, Petar Kirov 
[ZombineDev] wrote:
 On Thursday, 20 September 2018 at 10:50:49 UTC, Atila Neves 
 wrote:
 This pattern is incredibly easy to wrap and reuse as needed. I 
 would've done already if only I'd known  nogc was ignored as 
 well as pure.
It's a recent development: https://dlang.org/changelog/2.082.0#debug-unsafe :
 Unsafe code can now be used in debug blocks
https://dlang.org/changelog/2.079.0 :
 Bugzilla 16492: support  nogc in debug{} blocks
Ah. I was wondering how that passed me by! Thanks.
Sep 20 2018
parent Seb <seb wilzba.ch> writes:
On Thursday, 20 September 2018 at 13:21:21 UTC, Atila Neves wrote:
 On Thursday, 20 September 2018 at 12:41:07 UTC, Petar Kirov 
 [ZombineDev] wrote:
 On Thursday, 20 September 2018 at 10:50:49 UTC, Atila Neves 
 wrote:
 This pattern is incredibly easy to wrap and reuse as needed. 
 I would've done already if only I'd known  nogc was ignored 
 as well as pure.
It's a recent development: https://dlang.org/changelog/2.082.0#debug-unsafe :
 Unsafe code can now be used in debug blocks
https://dlang.org/changelog/2.079.0 :
 Bugzilla 16492: support  nogc in debug{} blocks
Ah. I was wondering how that passed me by! Thanks.
The only bit missing is `nothrow`: https://github.com/dlang/dmd/pull/8449
Sep 20 2018
prev sibling parent Per =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
On Friday, 7 September 2018 at 17:01:09 UTC, Meta wrote:
 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.
AFAICT, extending std.container with support for specifying you own nogc (malloc-based) allocators is one way of making `topNCopy` not use the GC.
Sep 19 2018
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Sep 07 2018
prev sibling next sibling parent Eugene Wissner <belka caraus.de> writes:
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 

 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.
Sep 07 2018
prev sibling parent Shachar Shemesh <shachar weka.io> writes:
I've got plenty to say, but here is the long and the short of it: Use Mecca.

On 07/09/18 19:44, Peter Alexander wrote:
 3. It was really frustrating that I had to make the compiler happy 

 code around to restructure things and wanted to make sure everything 
 continued working before all GC allocations were removed.
mecca.lib.reflection has "as". https://weka-io.github.io/mecca/docs/mecca/lib/reflection/as.html Here is how you use it: void fun() nogc { as!" nogc"( some code that is not nogc ); }
 
 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.
mecca.log has logging facilities that are nogc. Now, to be fair, they are not actually nogc, as by default it uses writeln. It is, however, annotated with nogc, for precisely the reasons you encountered. Shachar
Sep 19 2018