Conversation
The semaphore wakes a waiter by closing its channel, and the runtime queues it as runnext of the releaser's P, where it runs only once the releaser blocks or another P steals it. A releaser that keeps the CPU after Release therefore leaves the resource unused for as long as it runs. A Gosched after the release, taken only when someone is blocked in Acquire, hands the P over at once. On the new benchmark (4 resources, ~100 waiters, 1 ms hold, 1 ms of work after Release, GOMAXPROCS=4) this goes from 411-477 us/op to 254 us/op.
Owner
|
This change seems to have a catastrophic performance impact when under heavy CPU load. |
Author
|
I don't see how to fix it without pulling patched version of semphore into the codebase. Releaser keeping the CPU is not an obvious edge case. Doesn't look like it warrants the scale of required changes. I think I will address it downstream. Sorry for the noise. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #46.
Release,DestroyandHijackwake the first blocked acquirer by releasing the semaphore, which closes that waiter's channel. The runtime queues a goroutine woken that way asrunnextof the waker's P, where it runs only once the waker blocks or another P steals it. A releaser that keeps the CPU afterReleasetherefore leaves the resource unused for as long as it runs.The fix is a
runtime.Gosched()after the semaphore release, with the mutex already unlocked, taken only while a goroutine is blocked inAcquire; an atomic counter aroundacquireSem.Acquiresays whether one is. The uncontended path is unchanged.BenchmarkAcquire_ReleaseThenWork(first commit) is a pool of 4 under ~100 goroutines that hold for 1 ms and keep the CPU for 1 ms after releasing,-cpu 4 -count 3:The same effect on the bare
x/syncsemaphore, and the per-acquire residue it corresponds to, is in the reproduction attached to #46: https://gist.github.com/mikluko/57dbb8a46e2d38b9b8b56cb4d7675f77CreateResourceand the asynchronous construction ininitResourceValuerelease the semaphore too and are left alone: the constructing goroutine exits right after its release, andCreateResourceis a warm-up call.go vetreportspool_test.go:1308: call to (*testing.B).Fatal from a non-test goroutineon master already; not touched here.