runtime/debug: don't trigger a GC on SetGCPercent

Currently SetGCPercent forces a GC in order to recompute GC pacing.
Since we can now recompute pacing on the fly using gcSetTriggerRatio,
change SetGCPercent (really runtime.setGCPercent) to go through
gcSetTriggerRatio and not trigger a GC.

Fixes #19076.

Change-Id: Ib30d7ab1bb3b55219535b9f238108f3d45a1b522
Reviewed-on: https://go-review.googlesource.com/39835
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Rick Hudson <rlh@golang.org>
diff --git a/src/runtime/debug/garbage.go b/src/runtime/debug/garbage.go
index 27adc70..785e9d4 100644
--- a/src/runtime/debug/garbage.go
+++ b/src/runtime/debug/garbage.go
@@ -89,11 +89,7 @@
 // at startup, or 100 if the variable is not set.
 // A negative percentage disables garbage collection.
 func SetGCPercent(percent int) int {
-	old := setGCPercent(int32(percent))
-	if percent >= 0 {
-		runtime.GC()
-	}
-	return int(old)
+	return int(setGCPercent(int32(percent)))
 }
 
 // FreeOSMemory forces a garbage collection followed by an
diff --git a/src/runtime/debug/garbage_test.go b/src/runtime/debug/garbage_test.go
index 37417ac..acc781e 100644
--- a/src/runtime/debug/garbage_test.go
+++ b/src/runtime/debug/garbage_test.go
@@ -160,6 +160,8 @@
 	runtime.ReadMemStats(&ms)
 	ngc1 := ms.NumGC
 	SetGCPercent(10)
+	// It may require an allocation to actually force the GC.
+	setGCPercentSink = make([]byte, 1<<20)
 	runtime.ReadMemStats(&ms)
 	ngc2 := ms.NumGC
 	if ngc1 == ngc2 {
diff --git a/src/runtime/mgc.go b/src/runtime/mgc.go
index 8ec062a..5dc4170 100644
--- a/src/runtime/mgc.go
+++ b/src/runtime/mgc.go
@@ -178,20 +178,21 @@
 		throw("size of Workbuf is suboptimal")
 	}
 
-	_ = setGCPercent(readgogc())
+	// No sweep on the first cycle.
+	mheap_.sweepdone = 1
 
 	// Set a reasonable initial GC trigger.
 	memstats.triggerRatio = 7 / 8.0
-	memstats.gc_trigger = heapminimum
-	// Compute the goal heap size based on the trigger:
-	//   trigger = marked * (1 + triggerRatio)
-	//   marked = trigger / (1 + triggerRatio)
-	//   goal = marked * (1 + GOGC/100)
-	//        = trigger / (1 + triggerRatio) * (1 + GOGC/100)
-	memstats.next_gc = uint64(float64(memstats.gc_trigger) / (1 + memstats.triggerRatio) * (1 + float64(gcpercent)/100))
-	if gcpercent < 0 {
-		memstats.next_gc = ^uint64(0)
-	}
+
+	// Fake a heap_marked value so it looks like a trigger at
+	// heapminimum is the appropriate growth from heap_marked.
+	// This will go into computing the initial GC goal.
+	memstats.heap_marked = uint64(float64(heapminimum) / (1 + memstats.triggerRatio))
+
+	// Set gcpercent from the environment. This will also compute
+	// and set the GC trigger and goal.
+	_ = setGCPercent(readgogc())
+
 	work.startSema = 1
 	work.markDoneSema = 1
 }
@@ -226,12 +227,8 @@
 	}
 	gcpercent = in
 	heapminimum = defaultHeapMinimum * uint64(gcpercent) / 100
-	if memstats.triggerRatio > float64(gcpercent)/100 {
-		memstats.triggerRatio = float64(gcpercent) / 100
-	}
-	// This is either in gcinit or followed by a STW GC, both of
-	// which will reset other stats like memstats.gc_trigger and
-	// memstats.next_gc to appropriate values.
+	// Update pacing in response to gcpercent change.
+	gcSetTriggerRatio(memstats.triggerRatio)
 	unlock(&mheap_.lock)
 	return out
 }