diff options
| author | Austin Clements <austin@google.com> | 2015-04-22 14:42:26 -0400 |
|---|---|---|
| committer | Austin Clements <austin@google.com> | 2015-04-24 15:12:52 +0000 |
| commit | e870f06c3f49ed63960a2575e330c2c75fc54a34 (patch) | |
| tree | f93c2d455d8c60c49d1368fa3f29e73ce5598a32 /src/runtime/runtime2.go | |
| parent | da0e37fa8d284e92972877c4be3d031ecf1c8334 (diff) | |
| download | go-e870f06c3f49ed63960a2575e330c2c75fc54a34.tar.xz | |
runtime: yield time slice to most recently readied G
Currently, when the runtime ready()s a G, it adds it to the end of the
current P's run queue and continues running. If there are many other
things in the run queue, this can result in a significant delay before
the ready()d G actually runs and can hurt fairness when other Gs in
the run queue are CPU hogs. For example, if there are three Gs sharing
a P, one of which is a CPU hog that never voluntarily gives up the P
and the other two of which are doing small amounts of work and
communicating back and forth on an unbuffered channel, the two
communicating Gs will get very little CPU time.
Change this so that when G1 ready()s G2 and then blocks, the scheduler
immediately hands off the remainder of G1's time slice to G2. In the
above example, the two communicating Gs will now act as a unit and
together get half of the CPU time, while the CPU hog gets the other
half of the CPU time.
This fixes the problem demonstrated by the ping-pong benchmark added
in the previous commit:
benchmark old ns/op new ns/op delta
BenchmarkPingPongHog 684287 825 -99.88%
On the x/benchmarks suite, this change improves the performance of
garbage by ~6% (for GOMAXPROCS=1 and 4), and json by 28% and 36% for
GOMAXPROCS=1 and 4. It has negligible effect on heap size.
This has no effect on the go1 benchmark suite since those benchmarks
are mostly single-threaded.
Change-Id: I858a08eaa78f702ea98a5fac99d28a4ac91d339f
Reviewed-on: https://go-review.googlesource.com/9289
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Diffstat (limited to 'src/runtime/runtime2.go')
| -rw-r--r-- | src/runtime/runtime2.go | 15 |
1 files changed, 14 insertions, 1 deletions
diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index eacf5f094b..476108e36c 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -129,6 +129,9 @@ type guintptr uintptr func (gp guintptr) ptr() *g { return (*g)(unsafe.Pointer(gp)) } func (gp *guintptr) set(g *g) { *gp = guintptr(unsafe.Pointer(g)) } +func (gp *guintptr) cas(old, new guintptr) bool { + return casuintptr((*uintptr)(unsafe.Pointer(gp)), uintptr(old), uintptr(new)) +} type puintptr uintptr @@ -350,10 +353,20 @@ type p struct { goidcache uint64 goidcacheend uint64 - // Queue of runnable goroutines. + // Queue of runnable goroutines. Accessed without lock. runqhead uint32 runqtail uint32 runq [256]*g + // runnext, if non-nil, is a runnable G that was ready'd by + // the current G and should be run next instead of what's in + // runq if there's time remaining in the running G's time + // slice. It will inherit the time left in the current time + // slice. If a set of goroutines is locked in a + // communicate-and-wait pattern, this schedules that set as a + // unit and eliminates the (potentially large) scheduling + // latency that otherwise arises from adding the ready'd + // goroutines to the end of the run queue. + runnext guintptr // Available G's (status == Gdead) gfree *g |
