aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/mgcpacer_test.go
diff options
context:
space:
mode:
authorMichael Anthony Knyszek <mknyszek@google.com>2022-03-16 15:47:57 +0000
committerMichael Knyszek <mknyszek@google.com>2022-04-26 22:08:42 +0000
commite1b5f347e78c733bb0743df04c990e20f74bf188 (patch)
tree21f01ca7b096f632d97c7608f3aa470aff07521e /src/runtime/mgcpacer_test.go
parent226346bb763233ed9341bc1d829752628479845f (diff)
downloadgo-e1b5f347e78c733bb0743df04c990e20f74bf188.tar.gz
go-e1b5f347e78c733bb0743df04c990e20f74bf188.zip
runtime: reduce max idle mark workers during periodic GC cycles
This change reduces the maximum number of idle mark workers during periodic (currently every 2 minutes) GC cycles to 1. Idle mark workers soak up all available and unused Ps, up to GOMAXPROCS. While this provides some throughput and latency benefit in general, it can cause what appear to be massive CPU utilization spikes in otherwise idle applications. This is mostly an issue for *very* idle applications, ones idle enough to trigger periodic GC cycles. This spike also tends to interact poorly with auto-scaling systems, as the system might assume the load average is very low and suddenly see a massive burst in activity. The result of this change is not to bring down this 100% (of GOMAXPROCS) CPU utilization spike to 0%, but rather min(25% + 1/GOMAXPROCS*100%, 100%) Idle mark workers also do incur a small latency penalty as they must be descheduled for other work that might pop up. Luckily the runtime is pretty good about getting idle mark workers off of Ps, so in general the latency benefit from shorter GC cycles outweighs this cost. But, the cost is still non-zero and may be more significant in idle applications that aren't invoking assists and write barriers quite as often. We can't completely eliminate idle mark workers because they're currently necessary for GC progress in some circumstances. Namely, they're critical for progress when all we have is fractional workers. If a fractional worker meets its quota, and all user goroutines are blocked directly or indirectly on a GC cycle (via runtime.GOMAXPROCS, or runtime.GC), the program may deadlock without GC workers, since the fractional worker will go to sleep with nothing to wake it. Fixes #37116. For #44163. Change-Id: Ib74793bb6b88d1765c52d445831310b0d11ef423 Reviewed-on: https://go-review.googlesource.com/c/go/+/393394 Reviewed-by: Michael Pratt <mpratt@google.com> Run-TryBot: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
Diffstat (limited to 'src/runtime/mgcpacer_test.go')
-rw-r--r--src/runtime/mgcpacer_test.go64
1 files changed, 64 insertions, 0 deletions
diff --git a/src/runtime/mgcpacer_test.go b/src/runtime/mgcpacer_test.go
index b49e3a8d24..23628898d4 100644
--- a/src/runtime/mgcpacer_test.go
+++ b/src/runtime/mgcpacer_test.go
@@ -738,3 +738,67 @@ func FuzzPIController(f *testing.F) {
}
})
}
+
+func TestIdleMarkWorkerCount(t *testing.T) {
+ const workers = 10
+ c := NewGCController(100)
+ c.SetMaxIdleMarkWorkers(workers)
+ for i := 0; i < workers; i++ {
+ if !c.NeedIdleMarkWorker() {
+ t.Fatalf("expected to need idle mark workers: i=%d", i)
+ }
+ if !c.AddIdleMarkWorker() {
+ t.Fatalf("expected to be able to add an idle mark worker: i=%d", i)
+ }
+ }
+ if c.NeedIdleMarkWorker() {
+ t.Fatalf("expected to not need idle mark workers")
+ }
+ if c.AddIdleMarkWorker() {
+ t.Fatalf("expected to not be able to add an idle mark worker")
+ }
+ for i := 0; i < workers; i++ {
+ c.RemoveIdleMarkWorker()
+ if !c.NeedIdleMarkWorker() {
+ t.Fatalf("expected to need idle mark workers after removal: i=%d", i)
+ }
+ }
+ for i := 0; i < workers-1; i++ {
+ if !c.AddIdleMarkWorker() {
+ t.Fatalf("expected to be able to add idle mark workers after adding again: i=%d", i)
+ }
+ }
+ for i := 0; i < 10; i++ {
+ if !c.AddIdleMarkWorker() {
+ t.Fatalf("expected to be able to add idle mark workers interleaved: i=%d", i)
+ }
+ if c.AddIdleMarkWorker() {
+ t.Fatalf("expected to not be able to add idle mark workers interleaved: i=%d", i)
+ }
+ c.RemoveIdleMarkWorker()
+ }
+ // Support the max being below the count.
+ c.SetMaxIdleMarkWorkers(0)
+ if c.NeedIdleMarkWorker() {
+ t.Fatalf("expected to not need idle mark workers after capacity set to 0")
+ }
+ if c.AddIdleMarkWorker() {
+ t.Fatalf("expected to not be able to add idle mark workers after capacity set to 0")
+ }
+ for i := 0; i < workers-1; i++ {
+ c.RemoveIdleMarkWorker()
+ }
+ if c.NeedIdleMarkWorker() {
+ t.Fatalf("expected to not need idle mark workers after capacity set to 0")
+ }
+ if c.AddIdleMarkWorker() {
+ t.Fatalf("expected to not be able to add idle mark workers after capacity set to 0")
+ }
+ c.SetMaxIdleMarkWorkers(1)
+ if !c.NeedIdleMarkWorker() {
+ t.Fatalf("expected to need idle mark workers after capacity set to 1")
+ }
+ if !c.AddIdleMarkWorker() {
+ t.Fatalf("expected to be able to add idle mark workers after capacity set to 1")
+ }
+}