runtime: contention in runtime.(*mheap).freeSpan.func1 on mheap_.lock #61427
Labels
compiler/runtime
Issues related to the Go compiler and/or runtime.
NeedsInvestigation
Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Performance
Milestone
This is the second in what I think will be a series of three issue reports about contention I've seen on various runtime-internal singleton locks in an app that typically runs on dual-socket linux/amd64 machines with 96 (or sometimes 64) hardware threads.
This issue is about contention in
runtime.(*mheap).freeSpan.func1
onmheap_.lock
.I hope it'll be of interest to @mknyszek . Also CC @golang/runtime
Sorry about the somewhat outdated version. I haven't seen related issues go by, so I hope the info in this one is still of some use.
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
I don't know yet if the issue is present in Go 1.20 series or in the release candidates of the Go 1.21 series.
What operating system and processor architecture are you using (
go env
)?The app runs on Linux / x86_64, typically on machines with two processor sockets (NUMA). I don't have the output of
go env
from the app's runtime environment.What did you do?
A data-processing app received a higher volume of data than usual. Here are some more of its dimensions:
The average allocation size is about 128 bytes. Across the whole app, it averages around 4 GiB per second of allocations, or 16 kiB of allocations every 4 µs. Split across the 96 threads the runtime uses for the app (GOMAXPROCS == num hardware threads), that's 40 MiB per second per thread, an average of 3 µs between allocations, or 16 kiB of allocations every 400 µs.
What did you expect to see?
I expected to see very little contention on runtime-internal singleton locks.
What did you see instead?
Contention in
runtime.(*mheap).freeSpan.func1
makes it hard for callers ofruntime.mallocgc
to do their required sweep assist work. The contention is on these two lines:https://github.com/golang/go/blob/go1.19.6/src/runtime/mheap.go#L1486
https://github.com/golang/go/blob/go1.19.6/src/runtime/mheap.go#L1500
The code in
runtime.(*sweepLocked).sweep
closer to the root of the call stack looks interesting. It's the same in the development tip now as it was in Go 1.15. From my read of it, when a swept span is either completely or partially full, it gets pushed onto a lock-free stack. But if the swept span is completely empty, the resulting call tomheap_.freeSpan
requires taking the globalmheap_.lock
lock.@mknyszek , would it be possible for
mheap_.freeSpan
to use a lock-free structure (maybelfstack
) too?go/src/runtime/mgcsweep.go
Lines 751 to 768 in 5d481ab
Data set 39f2fa9279db66ec9e1b0a19b63bb1f479d827b39abfc67dbd715a4e18f7680b is from a instance of the app running on a 96-thread machine. The CPU profile ran for 5.16 seconds, during which it collected samples corresponding to a total 392 seconds of on-CPU time (an average of 76 on-CPU threads) and 98.16+73.70 = 171.86 seconds of time (an average of 33 on-CPU threads) in calls from
runtime.(*mheap).freeSpan.func1
to lock and unlockmheap_.lock
.The text was updated successfully, but these errors were encountered: