Bug 20219 – Idle D programs keep consuming CPU in Gcx.scanBackground
Status
RESOLVED
Resolution
FIXED
Severity
regression
Priority
P2
Component
druntime
Product
D
Version
D2
Platform
All
OS
All
Creation time
2019-09-17T01:44:21Z
Last change time
2019-09-27T10:07:46Z
Assigned to
No Owner
Creator
Vladimir Panteleev
Comments
Comment #0 by dlang-bugzilla — 2019-09-17T01:44:21Z
This program will keep consuming CPU cycles in the background, when it should be completely idle:
/////////////// test.d ///////////////
import core.memory;
import core.stdc.stdio;
import core.sys.posix.unistd;
void main()
{
printf("Creating garbage...\n");
foreach (n; 0 .. 1_000)
new uint[1_000_000];
printf("Collecting garbage...\n");
GC.collect();
printf("Sleeping...\n");
sleep(uint.max);
}
//////////////////////////////////////
Examining the program's state with a debugger reveals that the CPU consumption comes from a loop in Gcx.scanBackground. This function will wake up every 10 milliseconds to check if it has any work to do, then go back to sleep, until the garbage collector is deinitialized.
Even though the CPU consumption is small (about 0.5% per core on my machine), it can still add up when many otherwise-idle D programs are running, which would otherwise have no load impact on the system other than RAM usage.
More importantly, the timer is causing the CPU to wake up and thus exit a low power state frequently. This can significantly affect power consumption of portable / low-power devices. For this reason, I consider this to be a significant regression. If possible, the implementation should be changed so that the garbage collector notifies background threads that they have work to do using synchronization primitives like semaphores, and timers should not be used at all anywhere.
I should also note that it is atypical for system programming language runtimes to create runtime threads and leave them around, even if they are idle when the program is. These still consume some resources like PIDs, which are finite. Unless an uncompromising solution can be found, perhaps it would be appropriate to make parallel marking opt-in rather than opt-out.
To illustrate and extrapolate on the above: on a Linux machine, open htop, make sure that threads are visible (toggled with H) and kernel threads are hidden (toggled with K). Note that even though some processes (lines in white or black text) have threads attached to them (lines in green text), most do not. If all of the software on the system were to be hypothetically rewritten in D, every process would have a dozen threads attached to it, which to me doesn't seem like a great outcome.
Comment #1 by dlang-bot — 2019-09-17T07:06:57Z
@rainers created dlang/druntime pull request #2802 "Issue 20219 - Idle D programs keep consuming CPU in Gcx.scanBackground" mentioning this issue:
- Issue 20219 - Idle D programs keep consuming CPU in Gcx.scanBackground
background scan threads now wait indefinitely, with termination continuously triggering the condition until all threads have woken up
https://github.com/dlang/druntime/pull/2802
Comment #2 by r.sagitario — 2019-09-17T07:15:12Z
I've been bothered by the idle CPU time recently too.
Not sure what to do about the additional threads staying around forever, maybe they could terminate after some time of not running, but that adds considerable overhead when restarting them.
It seems that the parallel marking has worked out without much troubles (better than expected). When making it opt-in, only few programs would actually benefit from that, because people won't bother to change the defaults.
Comment #3 by dlang-bugzilla — 2019-09-17T14:25:41Z
(In reply to Rainer Schuetze from comment #2)
> Not sure what to do about the additional threads staying around forever,
> maybe they could terminate after some time of not running, but that adds
> considerable overhead when restarting them.
How costly is this overhead?
I think your suggestion is a good idea, and we should balance the time the threads stay idle with the overhead of starting them. E.g.: let them shut down if they have been idle for more than 100x-1000x the time that would be needed to start them again.
(In reply to Rainer Schuetze from comment #2)
> When making it opt-in, only few programs would
> actually benefit from that, because people won't bother to change the
> defaults.
Not sure what you're trying to say, as this argument applies to both opt-in and opt-out, with each being a compromise.
What I'm worried about of, if this were to become normalized, reactions like "Why does this tiny program I just installed need so many threads? Oh right, it's written in D, that language with that bloated runtime and garbage collector which needs a dozen threads to work properly. What a mess, why hasn't someone rewritten it in Rust yet!".
Comment #4 by r.sagitario — 2019-09-18T06:41:21Z
(In reply to Vladimir Panteleev from comment #3)
> How costly is this overhead?
I don't have hard numbers, but IIRC the overhead was a couple of ms for tests that only ever run a single collection.
>
> I think your suggestion is a good idea, and we should balance the time the
> threads stay idle with the overhead of starting them. E.g.: let them shut
> down if they have been idle for more than 100x-1000x the time that would be
> needed to start them again.
Possible, but I'm not sure it is worth the effort. The threads use only a small stack of 16kB and are now just waiting for a common event.
>
> (In reply to Rainer Schuetze from comment #2)
> > When making it opt-in, only few programs would
> > actually benefit from that, because people won't bother to change the
> > defaults.
>
> Not sure what you're trying to say, as this argument applies to both opt-in
> and opt-out, with each being a compromise.
True, it's a compromise trading some thread handles and memory for better performance in most cases. I suspect most people (and especially the users of the program more than the developer) want the program to run faster, so the default should be that.
>
> What I'm worried about of, if this were to become normalized, reactions like
> "Why does this tiny program I just installed need so many threads? Oh right,
> it's written in D, that language with that bloated runtime and garbage
> collector which needs a dozen threads to work properly. What a mess, why
> hasn't someone rewritten it in Rust yet!".
When I start any program in a debugger on Windows, I see 3 additional threads already running for that process when entering WinMain. I haven't seen any complaints about that.
Comment #5 by dlang-bugzilla — 2019-09-18T13:46:40Z
(In reply to Rainer Schuetze from comment #4)
> I don't have hard numbers, but IIRC the overhead was a couple of ms for
> tests that only ever run a single collection.
Then, would you agree that terminating the threads after a few seconds of inactivity would be appropriate?
> Possible, but I'm not sure it is worth the effort. The threads use only a
> small stack of 16kB and are now just waiting for a common event.
I can look into implementing this if we agree otherwise.
> True, it's a compromise trading some thread handles and memory for better
> performance in most cases. I suspect most people (and especially the users
> of the program more than the developer) want the program to run faster, so
> the default should be that.
I think I would agree that GC speed is overall more important, though I don't think we have the data for how D is used to make hard conclusions about which tradeoff is better. It might be possible that GC speed would not be a large factor of the performance of a typical D program? Most D users already must undergo an additional step for their D programs to perform reasonably, which is to switch to the GDC or LDC compiler.
> When I start any program in a debugger on Windows, I see 3 additional
> threads already running for that process when entering WinMain. I haven't
> seen any complaints about that.
The situation on other platforms might very well be different. I don't think Windows has received nearly the same level of scrutiny about not wastefully using resources as FOSS operating systems. Would you suggest making the default behavior depend on the current OS?
Comment #6 by r.sagitario — 2019-09-18T20:56:12Z
(In reply to Vladimir Panteleev from comment #5)
> Then, would you agree that terminating the threads after a few seconds of
> inactivity would be appropriate?
I'd be ok with this (or maybe a slightly larger timeout) if it doesn't complicate the handling too much.
> The situation on other platforms might very well be different. I don't think
> Windows has received nearly the same level of scrutiny about not wastefully
> using resources as FOSS operating systems. Would you suggest making the
> default behavior depend on the current OS?
Fine with me. My initial suggestion was actually to make it opt-in (https://github.com/dlang/druntime/pull/2514#issuecomment-478285235), but there were no complaints while it was enabled ;-) But maybe it wasn't stress tested enough.
Comment #7 by dlang-bot — 2019-09-27T10:07:46Z
dlang/druntime pull request #2802 "Issue 20219 - Idle D programs keep consuming CPU in Gcx.scanBackground" was merged into stable:
- 18ca55405eed87ce45e84e11dba14debf0346266 by Rainer Schuetze:
fix issue 20219 - Idle D programs keep consuming CPU in Gcx.scanBackground
background scan threads now wait indefinitely, with termination continuously triggering the condition until all threads have woken up
https://github.com/dlang/druntime/pull/2802