Skip to content

Commit 50048a4

Browse files
runtime: add as many extra M's as needed
When a non-Go thread calls into Go, the runtime needs an M to run the Go code. The runtime keeps a list of extra M's available. When the last extra M is allocated, the needextram field is set to tell it to allocate a new extra M as soon as it is running in Go. This ensures that an extra M will always be available for the next thread. However, if many threads need an extra M at the same time, this serializes them all. One thread will get an extra M with the needextram field set. All the other threads will see that there is no M available and will go to sleep. The one thread that succeeded will create a new extra M. One lucky thread will get it. All the other threads will see that there is no M available and will go to sleep. The effect is thundering herd, as all the threads looking for an extra M go through the process one by one. This seems to have a particularly bad effect on the FreeBSD scheduler for some reason. With this change, we track the number of threads waiting for an M, and create all of them as soon as one thread gets through. This still means that all the threads will fight for the lock to pick up the next M. But at least each thread that gets the lock will succeed, instead of going to sleep only to fight again. This smooths out the performance greatly on FreeBSD, reducing the average wall time of `testprogcgo CgoCallbackGC` by 74%. On GNU/Linux the average wall time goes down by 9%. Fixes #13926 Fixes #16396 Change-Id: I6dc42a4156085a7ed4e5334c60b39db8f8ef8fea Reviewed-on: https://go-review.googlesource.com/25047 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Dmitry Vyukov <[email protected]>
1 parent 883e128 commit 50048a4

File tree

2 files changed

+29
-2
lines changed

2 files changed

+29
-2
lines changed

src/runtime/cgocall.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
package runtime
8181

8282
import (
83+
"runtime/internal/atomic"
8384
"runtime/internal/sys"
8485
"unsafe"
8586
)
@@ -176,7 +177,7 @@ func cgocallbackg(ctxt uintptr) {
176177

177178
func cgocallbackg1(ctxt uintptr) {
178179
gp := getg()
179-
if gp.m.needextram {
180+
if gp.m.needextram || atomic.Load(&extraMWaiters) > 0 {
180181
gp.m.needextram = false
181182
systemstack(newextram)
182183
}

src/runtime/proc.go

+27-1
Original file line numberDiff line numberDiff line change
@@ -1389,10 +1389,27 @@ func needm(x byte) {
13891389

13901390
var earlycgocallback = []byte("fatal error: cgo callback before cgo call\n")
13911391

1392-
// newextram allocates an m and puts it on the extra list.
1392+
// newextram allocates m's and puts them on the extra list.
13931393
// It is called with a working local m, so that it can do things
13941394
// like call schedlock and allocate.
13951395
func newextram() {
1396+
c := atomic.Xchg(&extraMWaiters, 0)
1397+
if c > 0 {
1398+
for i := uint32(0); i < c; i++ {
1399+
oneNewExtraM()
1400+
}
1401+
} else {
1402+
// Make sure there is at least one extra M.
1403+
mp := lockextra(true)
1404+
unlockextra(mp)
1405+
if mp == nil {
1406+
oneNewExtraM()
1407+
}
1408+
}
1409+
}
1410+
1411+
// oneNewExtraM allocates an m and puts it on the extra list.
1412+
func oneNewExtraM() {
13961413
// Create extra goroutine locked to extra m.
13971414
// The goroutine is the context in which the cgo callback will run.
13981415
// The sched.pc will never be returned to, but setting it to
@@ -1485,6 +1502,7 @@ func getm() uintptr {
14851502
}
14861503

14871504
var extram uintptr
1505+
var extraMWaiters uint32
14881506

14891507
// lockextra locks the extra list and returns the list head.
14901508
// The caller must unlock the list by storing a new list head
@@ -1495,6 +1513,7 @@ var extram uintptr
14951513
func lockextra(nilokay bool) *m {
14961514
const locked = 1
14971515

1516+
incr := false
14981517
for {
14991518
old := atomic.Loaduintptr(&extram)
15001519
if old == locked {
@@ -1503,6 +1522,13 @@ func lockextra(nilokay bool) *m {
15031522
continue
15041523
}
15051524
if old == 0 && !nilokay {
1525+
if !incr {
1526+
// Add 1 to the number of threads
1527+
// waiting for an M.
1528+
// This is cleared by newextram.
1529+
atomic.Xadd(&extraMWaiters, 1)
1530+
incr = true
1531+
}
15061532
usleep(1)
15071533
continue
15081534
}

0 commit comments

Comments
 (0)