Skip to content

Commit 7244e92

Browse files
mknyszekgopherbot
authored andcommitted
runtime: eliminate _Psyscall
This change eliminates the _Psyscall state by using synchronization on the G status _Gsyscall to make syscalls work instead. This removes an atomic Store and an atomic CAS on the syscall path, which reduces syscall and cgo overheads. It also simplifies the syscall paths quite a bit. The one danger with this change is that we have a new combination of states that was previously impossible. There are brief windows where it's possible to observe a goroutine in _Grunning but without a P. This change is careful to hide this detail from the execution tracer, but it may have unexpected effects in the rest of the runtime, making this change somewhat risky. goos: linux goarch: amd64 pkg: internal/runtime/cgobench cpu: AMD EPYC 7B13 │ before.out │ after.out │ │ sec/op │ sec/op vs base │ CgoCall-64 43.69n ± 1% 35.83n ± 1% -17.99% (p=0.002 n=6) CgoCallParallel-64 5.306n ± 1% 5.338n ± 1% ~ (p=0.132 n=6) Change-Id: I4551afc1eea0c1b67a0b2dd26b0d49aa47bf1fb8 Reviewed-on: https://go-review.googlesource.com/c/go/+/646198 Auto-Submit: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com>
1 parent 5ef19c0 commit 7244e92

File tree

12 files changed

+508
-312
lines changed

12 files changed

+508
-312
lines changed

src/runtime/crash_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,9 @@ func TestPanicInlined(t *testing.T) {
788788
// Test for issues #3934 and #20018.
789789
// We want to delay exiting until a panic print is complete.
790790
func TestPanicRace(t *testing.T) {
791+
if race.Enabled {
792+
t.Skip("racefini exits program before main can delay exiting")
793+
}
791794
testenv.MustHaveGoRun(t)
792795

793796
exe, err := buildTestProg(t, "testprog")

src/runtime/heapdump.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,14 @@ func dumpgs() {
412412
forEachG(func(gp *g) {
413413
status := readgstatus(gp) // The world is stopped so gp will not be in a scan state.
414414
switch status {
415+
case _Grunning:
416+
// Dump goroutine if it's _Grunning only during a syscall. This is safe
417+
// because the goroutine will just park without mutating its stack, since
418+
// the world is stopped.
419+
if gp.syscallsp != 0 {
420+
dumpgoroutine(gp)
421+
}
422+
fallthrough
415423
default:
416424
print("runtime: unexpected G.status ", hex(status), "\n")
417425
throw("dumpgs in STW - bad status")

src/runtime/metrics.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -818,9 +818,12 @@ func (a *schedStatsAggregate) compute() {
818818
a.gCreated += p.goroutinesCreated
819819
switch p.status {
820820
case _Prunning:
821-
a.gRunning++
822-
case _Psyscall:
823-
a.gNonGo++
821+
if thread, ok := setBlockOnExitSyscall(p); ok {
822+
thread.resume()
823+
a.gNonGo++
824+
} else {
825+
a.gRunning++
826+
}
824827
case _Pgcstop:
825828
// The world is stopping or stopped.
826829
// This is fine. The results will be
@@ -847,7 +850,7 @@ func (a *schedStatsAggregate) compute() {
847850
// Global run queue.
848851
a.gRunnable += uint64(sched.runq.size)
849852

850-
// Account for Gs that are in _Gsyscall without a P in _Psyscall.
853+
// Account for Gs that are in _Gsyscall without a P.
851854
nGsyscallNoP := sched.nGsyscallNoP.Load()
852855

853856
// nGsyscallNoP can go negative during temporary races.

src/runtime/mprof.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1535,7 +1535,18 @@ func doRecordGoroutineProfile(gp1 *g, pcbuf []uintptr) {
15351535
// everything else, we just don't record the stack in the profile.
15361536
return
15371537
}
1538-
if readgstatus(gp1) == _Grunning {
1538+
// Double-check that we didn't make a grave mistake. If the G is running then in
1539+
// general, we cannot safely read its stack.
1540+
//
1541+
// However, there is one case where it's OK. There's a small window of time in
1542+
// exitsyscall where a goroutine could be in _Grunning as it's exiting a syscall.
1543+
// This is OK because goroutine will not exit the syscall until it passes through
1544+
// a call to tryRecordGoroutineProfile. (An explicit one on the fast path, an
1545+
// implicit one via the scheduler on the slow path.)
1546+
//
1547+
// This is also why it's safe to check syscallsp here. The syscall path mutates
1548+
// syscallsp only after passing through tryRecordGoroutineProfile.
1549+
if readgstatus(gp1) == _Grunning && gp1.syscallsp == 0 {
15391550
print("doRecordGoroutineProfile gp1=", gp1.goid, "\n")
15401551
throw("cannot read stack of running goroutine")
15411552
}

src/runtime/mstats.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,7 @@ type cpuStats struct {
914914
ScavengeTotalTime int64
915915

916916
IdleTime int64 // Time Ps spent in _Pidle.
917-
UserTime int64 // Time Ps spent in _Prunning or _Psyscall that's not any of the above.
917+
UserTime int64 // Time Ps spent in _Prunning that's not any of the above.
918918

919919
TotalTime int64 // GOMAXPROCS * (monotonic wall clock time elapsed)
920920
}
@@ -976,7 +976,7 @@ func (s *cpuStats) accumulate(now int64, gcMarkPhase bool) {
976976
// Compute userTime. We compute this indirectly as everything that's not the above.
977977
//
978978
// Since time spent in _Pgcstop is covered by gcPauseTime, and time spent in _Pidle
979-
// is covered by idleTime, what we're left with is time spent in _Prunning and _Psyscall,
979+
// is covered by idleTime, what we're left with is time spent in _Prunning,
980980
// the latter of which is fine because the P will either go idle or get used for something
981981
// else via sysmon. Meanwhile if we subtract GC time from whatever's left, we get non-GC
982982
// _Prunning time. Note that this still leaves time spent in sweeping and in the scheduler,

src/runtime/os_windows.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@ type mOS struct {
191191
// complete.
192192
//
193193
// TODO(austin): We may not need this if preemption were more
194-
// tightly synchronized on the G/P status and preemption
195-
// blocked transition into _Gsyscall/_Psyscall.
194+
// tightly synchronized on the G status and preemption
195+
// blocked transition into _Gsyscall.
196196
preemptExtLock uint32
197197
}
198198

src/runtime/preempt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ func resumeG(state suspendGState) {
286286
//
287287
//go:nosplit
288288
func canPreemptM(mp *m) bool {
289-
return mp.locks == 0 && mp.mallocing == 0 && mp.preemptoff == "" && mp.p.ptr().status == _Prunning
289+
return mp.locks == 0 && mp.mallocing == 0 && mp.preemptoff == "" && mp.p.ptr().status == _Prunning && mp.curg != nil && readgstatus(mp.curg)&^_Gscan != _Gsyscall
290290
}
291291

292292
//go:generate go run mkpreempt.go

0 commit comments

Comments
 (0)