-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
gh-125997: suggest efficient alternatives for time.sleep(0)
#128752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The implementation of `time.sleep()` changed in Python 3.11 and relies on `clock_nanosleep()` or `nanosleep()` since then. This introduced a regression in code using `time.sleep(0)` for a syscall "no-op", polling or momentarily suspending the caller's thread. To alleviate the performance regression, we suggest some alternatives depending on the caller's needs.
@hauntsaninja and @charles-cooper: I would appreciate if you can share your thoughts on those suggestions as you were also involved in the other PR's discussion. |
i think it's interesting that besides that, i would just mention that clock_nanosleep may sleep at least 50us on linux. (i mean this is arguably a linux bug, but it is kind of surprising to users that sleep(0) sleeps a lot longer than one might expect). |
Actually,
While it's mentioned like that, I think it's wrong in the first place to rely on Another solution is to check if we're on FreeBSD or not, but this makes maintainance harder. Also, for those reading the PR only, the sleep gap only happens at t = 0 and not at t = 1e-9 (for t = 1e-9, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this PR looks great, I think this is right approach.
I would not document current implementation choices of default Linux scheduler, especially since we've currently only had one report in >2 years. (If it does come up again, we can mention os.sched_setscheduler
/ PR_SET_TIMERSLACK
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
||
To emulate a "no-op", use :keyword:`pass` instead of ``time.sleep(0)``. | ||
|
||
To voluntarily relinquish the CPU, specify a real-time :ref:`scheduling |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of just repeating "relinquish the CPU" from sched_yield
's docs, I think it's worth being more specific. Something like "allow other threads to execute" and/or "temporarily release the GIL" could be useful.
The other issue here is that sched_yield
is specific to Unix systems--what are Windows users supposed to use if they want to explicitly let another thread take the GIL, if not time.sleep()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They use time.sleep(0)
. The note is for the Unix section. On Windows, Sleep(0)
relinquishes the CPU: https://learn.microsoft.com/en-gb/windows/win32/api/synchapi/nf-synchapi-sleep?redirectedfrom=MSDN.
A value of zero causes the thread to relinquish the remainder of its time slice to any other thread that is ready to run. If there are no other threads ready to run, the function returns immediately, and the thread continues execution. Windows XP: A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution. This behavior changed starting with Windows Server 2003.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I can add "On non-Windows platforms" if you want?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, got it. I didn't see that this was explicitly the Unix section, I thought you were just reformatting. (I still do think we should mention the GIL here, but it's up to you.)
Anyways, this is out of the scope of this PR, but this kind of situation probably isn't ideal for users. You have to maintain two code paths: one to call time.sleep(0)
on Windows, and then os.sched_yield
on Linux. Do you think it's a good idea to add a dedicated function for that in a follow-up issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. I don't know much about Windows users but maybe os.sched_yield()
could, call Sleep(0)
on Windows? Considering that no one ever complained about this specifically (what users complain is that it takes too much time, and probably don't really care about what we're actually doing behind the scene), I think we shouldn't bother for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably because it's difficult to notice--in practice, people use time.sleep(0)
on both systems, because the performance issue on Linux generally isn't clear, especially for multithreaded programs. My theory is that providing a dedicated function will get people to notice the issue more. I'm speculating though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could open a DPO thread for that. Namely, whether to provide a Windows-equivalent of os.sched_yield()
but that does not have the meaning of a scheduling policy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO you're trying to solve a non-existent problem. time.sleep(0)
is efficient and does what the developer expects. It's just that os.sched_yield()
can be even more efficient on Linux. IMO time.sleep(0)
is the portable "pass the CPU to another thread", even if it's not "optimal" (on Linux).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really need the most efficient "yield":
if hasattr(os, 'sched_yield'):
def portable_efficient_yield():
os.sched_yield()
else:
def portable_efficient_yield():
time.sleep(0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I made a DPO thread, feel free to post concerns there: https://discuss.python.org/t/platform-agnostic-yielding-of-the-gil/77006/
I'm in favor of a function other than time.sleep(0)
for yielding, because it's not particularly clear that sleep(0)
would actually do something useful, but we're killing two birds with one stone because of the performance.
@vstinner I plan to merge this PR with no commit message and the current PR title. I don't want to expand more on the rationale in the commit and I think it's better to look at the issue directly. WDYT? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Go ahead.
Thanks @picnixz for the PR 🌮🎉.. I'm working now to backport this PR to: 3.13. |
Thanks @picnixz for the PR 🌮🎉.. I'm working now to backport this PR to: 3.12. |
…ythonGH-128752) (cherry picked from commit f4afaa6) Co-authored-by: Bénédikt Tran <[email protected]>
Sorry, @picnixz, I could not cleanly backport this to
|
GH-128984 is a backport of this pull request to the 3.13 branch. |
GH-128985 is a backport of this pull request to the 3.12 branch. |
After a discussion with Victor and because #128274 is not convincing enough, we decide to:
time.sleep()
, andtime.sleep(0)
.See #125997 (comment) for more details.
time.sleep(0)
is slower on Python 3.11 than on Python 3.10 #125997📚 Documentation preview 📚: https://cpython-previews--128752.org.readthedocs.build/