You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
time.sleep(0) is slower on Python 3.11 than on Python 3.10 #125997
~ $ time python3.10 relax.py
real 0m0.643s
user 0m0.345s
sys 0m0.314s
~ $ time python3.11 relax.py
real 0m52.212s
user 0m0.744s
sys 0m1.115s
~ $ time python3.12 relax.py
real 0m52.826s
user 0m1.317s
sys 0m0.997s
$ time python3.11 relax.py
python3.11 relax.py 1.07s user 1.54s system 4% cpu 55.396 total
$ time python3.12 relax.py
python3.12 relax.py 2.04s user 0.47s system 4% cpu 55.842 total
Though, timeit does pick up a tiny slowdown:
$ cat relax.py | python3.11 -m timeit
20000000 loops, best of 5: 10.8 nsec per loop
$ cat relax.py | python3.12 -m timeit
50000000 loops, best of 5: 7.47 nsec per loop
Unfortunately, 3.11 is security-only, so there's nothing that can get fixed here unless this is somehow causing a security issue somewhere.
python -m timeit -s 'import time''[time.sleep(0) for _ in range(1_000_000)]'# 3.10: 1 loop, best of 5: 631 msec per loop# 3.11: 1 loop, best of 5: 550 msec per loop# 3.12: 1 loop, best of 5: 556 msec per loop
I can reproduce on Linux (but not on macOS) — sleep goes from comfortably under 1us to >50us
I'm assuming this is caused by this change: #65501 (it's also mentioned in the docs for time.sleep).
Reading online, it sounds like you could get a lower interval by fiddling with the Linux scheduler.
Unfortunately, 3.11 is security-only, so there's nothing that can get fixed here
The change is present in Python 3.11 onwards, so it's possible we could change something here. That said, unless someone has a concrete change to suggest, this issue probably isn't going to go anywhere.
The change is present in Python 3.11 onwards, so it's possible we could change something here. That said, unless someone has a concrete change to suggest, this issue probably isn't going to go anywhere.
Oh, I do see that in the original report now. I thought this only affected 3.11, my bad! But yeah, I doubt there's anything that can be done here. Maybe this is somehow an upstream Linux problem.
An alternative would be to implement time.sleep(0) as calling sched_yield(). The problem is that I'm not sure if sched_yield() semantics is the one expected by time.sleep(0) callers?
The problem is that I'm not sure if sched_yield() semantics is the one expected by time.sleep(0) callers?
sched_yield() used with a non-deterministic scheduling policy (which is the default AFAIR) is not recommended (see its manpage). Relinquishing the CPU using time.sleep(0) is a Windows-only implementation (though I don't know why it was designed like this).
As a caller, I would expect:
time.sleep(0) to use sleep(), nanosleep(), or clock_nanosleep(). In this case, I shouldn't expect time.sleep(0) to be faster than a pure C call to clock_nanosleep().
Alternatively, it should immediately return (no call to any libc function). Now, do we want to specialize this or not? for now, I decided not to and suggested some alternatives. I see two cases: either the caller writes time.sleep(0) (explicitly) and this can be changed into pass or something else. Or this is something like time.sleep(s) where s = 0. In this case, I think the caller does not necessarily know what s is and thus expects that some libc function is called.
What I however think is that time.sleep() was previously incorrectly using select(). We should have always use a sleep function and not emulating a sleep via select() or sched_yield(). The purpose of those functions is different than "sleeping". The former is for waiting for file descriptors to be ready (but we're actually using the timeout value to emulate this) while the latter is to voluntarily relinquish the CPU. So both are distinct from just "sleeping" (although sched_yield() would be closer to what is expected, but since it requires a real-time scheduling policy, we shouldn't use it as well).
Note that using select() with very small sleep values also gives the same performance as with clock_nanosleep() and nanosleep(). The gap is only present when we use a sleep time of 0.
Since the behaviour of sleep functions is also platform dependent and since semantically correct alternatives for realistic use cases exist (pass for a no-op, select.poll.poll(0) for polling, sched_yield() to explicitly relinquish the CPU), we eventually decided not to revert to the previous behaviour (namely using select() to emulate a fast sleep).
Sorry for the inconvenience, but I believe that the fact that nanosleep(t) and clock_nanosleep(t) are much slower than expected for t = 0 is something that should be addressed on the kernel's side rather than Python doing some hacky things (for instance, FreeBSD does not seem to suffer from this issue).
Therefore, I will consider this issue to be resolved, albeit not with the desired outcome but probably the best outcome from a standard library point of view (also, the fact that this was only reported recently seems to indicate that this regression likely passed unnoticed).
Activity
dg-pb commentedon Oct 26, 2024
A while ago did some benchmarking on 3.11 and 3.12 with
sleep(on OSX) and it did seem unreasonably slow.Was expecting nano-seconds, but was in micro-second order.
Just assumed that this is how it should be.
But if this is actually a fixable regression, it would be great.
ZeroIntensity commentedon Oct 26, 2024
I wasn't able to reproduce this on my end:
Though,
timeitdoes pick up a tiny slowdown:Unfortunately, 3.11 is security-only, so there's nothing that can get fixed here unless this is somehow causing a security issue somewhere.
dg-pb commentedon Oct 26, 2024
Can not reproduce on OSX either:
charles-cooper commentedon Oct 26, 2024
hmm -- fwiw, here is my CPU info (from
cat /proc/cpuinfo):i am reproducing this in python3.13 as well.
and using
timeit:charles-cooper commentedon Oct 26, 2024
i am reproducing on another linux machine, too - this time with ubuntu 24.04
hauntsaninja commentedon Oct 26, 2024
I can reproduce on Linux (but not on macOS) — sleep goes from comfortably under 1us to >50us
I'm assuming this is caused by this change: #65501 (it's also mentioned in the docs for
time.sleep).Reading online, it sounds like you could get a lower interval by fiddling with the Linux scheduler.
The change is present in Python 3.11 onwards, so it's possible we could change something here. That said, unless someone has a concrete change to suggest, this issue probably isn't going to go anywhere.
ZeroIntensity commentedon Oct 26, 2024
Oh, I do see that in the original report now. I thought this only affected 3.11, my bad! But yeah, I doubt there's anything that can be done here. Maybe this is somehow an upstream Linux problem.
[-]time.sleep regression in 3.11[/-][+]time.sleep regression in 3.11 onwards[/+]13 remaining items
vstinner commentedon Jan 7, 2025
An alternative would be to implement
time.sleep(0)as callingsched_yield(). The problem is that I'm not sure ifsched_yield()semantics is the one expected bytime.sleep(0)callers?[-]`time.sleep(0)` regression in 3.11 onwards[/-][+]`time.sleep(0)` is slower on Python 3.11 than on Python 3.10[/+]picnixz commentedon Jan 12, 2025
sched_yield()used with a non-deterministic scheduling policy (which is the default AFAIR) is not recommended (see its manpage). Relinquishing the CPU usingtime.sleep(0)is a Windows-only implementation (though I don't know why it was designed like this).As a caller, I would expect:
time.sleep(0)to usesleep(),nanosleep(), orclock_nanosleep(). In this case, I shouldn't expecttime.sleep(0)to be faster than a pure C call toclock_nanosleep().Alternatively, it should immediately return (no call to any libc function). Now, do we want to specialize this or not? for now, I decided not to and suggested some alternatives. I see two cases: either the caller writes
time.sleep(0)(explicitly) and this can be changed intopassor something else. Or this is something liketime.sleep(s)wheres = 0. In this case, I think the caller does not necessarily know what s is and thus expects that some libc function is called.What I however think is that
time.sleep()was previously incorrectly usingselect(). We should have always use a sleep function and not emulating a sleep viaselect()orsched_yield(). The purpose of those functions is different than "sleeping". The former is for waiting for file descriptors to be ready (but we're actually using the timeout value to emulate this) while the latter is to voluntarily relinquish the CPU. So both are distinct from just "sleeping" (althoughsched_yield()would be closer to what is expected, but since it requires a real-time scheduling policy, we shouldn't use it as well).Note that using
select()with very small sleep values also gives the same performance as withclock_nanosleep()andnanosleep(). The gap is only present when we use a sleep time of 0.gh-125997: Increase test coverage for `time.sleep()` (#128751)
pythongh-125997: Increase test coverage for `time.sleep()` (pythonGH-…
pythongh-125997: Increase test coverage for `time.sleep()` (pythonGH-…
[3.12] gh-125997: Increase test coverage for `time.sleep()` (GH-128751)…
[3.13] gh-125997: Increase test coverage for `time.sleep()` (GH-128751)…
gh-125997: suggest efficient alternatives for `time.sleep(0)` (#128752)
pythongh-125997: suggest efficient alternatives for `time.sleep(0)` (p…
[3.13] gh-125997: suggest efficient alternatives for `time.sleep(0)` (G…
[3.12] gh-125997: suggest efficient alternatives for `time.sleep(0)` (G…
pythongh-125997: suggest efficient alternatives for `time.sleep(0)` (p…
picnixz commentedon Jan 21, 2025
Since the behaviour of
sleepfunctions is also platform dependent and since semantically correct alternatives for realistic use cases exist (passfor a no-op,select.poll.poll(0)for polling,sched_yield()to explicitly relinquish the CPU), we eventually decided not to revert to the previous behaviour (namely usingselect()to emulate a fast sleep).Sorry for the inconvenience, but I believe that the fact that
nanosleep(t)andclock_nanosleep(t)are much slower than expected fort = 0is something that should be addressed on the kernel's side rather than Python doing some hacky things (for instance, FreeBSD does not seem to suffer from this issue).Therefore, I will consider this issue to be resolved, albeit not with the desired outcome but probably the best outcome from a standard library point of view (also, the fact that this was only reported recently seems to indicate that this regression likely passed unnoticed).