Skip to content

Commit 40155ff

Browse files
committed
optimize time.sleep(0) path
1 parent 23b4740 commit 40155ff

File tree

1 file changed

+20
-28
lines changed

1 file changed

+20
-28
lines changed

Modules/timemodule.c

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ module time
7373
/* Forward declarations */
7474
static int pysleep(PyTime_t timeout);
7575
#ifndef MS_WINDOWS
76-
static int pysleep_zero_posix(void);
76+
static int pysleep_zero_posix(void); // see gh-125997
7777
#endif
7878

7979

@@ -2218,7 +2218,7 @@ pysleep(PyTime_t timeout)
22182218
assert(timeout >= 0);
22192219
assert(!PyErr_Occurred());
22202220
#ifndef MS_WINDOWS
2221-
if (timeout == 0) {
2221+
if (timeout == 0) { // gh-125997
22222222
return pysleep_zero_posix();
22232223
}
22242224
#endif
@@ -2408,35 +2408,25 @@ pysleep(PyTime_t timeout)
24082408
//
24092409
// Rationale
24102410
// ---------
2411-
// time.sleep(0) accumulates delays if we use nanosleep() or clock_nanosleep().
2412-
// To avoid this pitfall, we may either use select(0, NULL, NULL, NULL, &zero)
2413-
// or sched_yield(). The former is implementation-sensitive while the latter
2414-
// would explicit relinquish the CPU but is more portable [1].
2415-
//
2416-
// While select() is less portable due to various implementation details
2417-
// it is slightly faster [2]. In addition, implicitly calling the kernel's
2418-
// algorithm in time.sleep(0) may not be what non-Windows users expect [3].
2419-
//
2420-
// Therefore, we opt for a solution based on select() instead of sched_yield().
2421-
//
2422-
// [1] On Linux, calling sched_yield() causes the kernel's scheduling algorithm
2423-
// to run as well and could be inefficient in terms of CPU consumption if
2424-
// time.sleep(0) is successively called multiple times.
2425-
//
2426-
// [2] Experimentally, the CPU consumption of a sched_yield() solution is
2427-
// similar to that one based on select(), albeit slightly slower.
2428-
//
2429-
// [3] sched_yield() is not recommended when resources needed by other
2430-
// schedulable threads are still held by the caller (which may be
2431-
// the case) and using it with with nondeterministic scheduling policies
2432-
// such as SCHED_OTHER (which is the default) results in an unspecified
2433-
// behaviour.
2411+
// time.sleep(0) accumulates delays in the generic implementation, but we can
2412+
// skip some calls to `PyTime_Monotonic()` and other checks when the timeout
2413+
// is zero. For details, see https://github.com/python/cpython/pull/128274.
24342414
static int
24352415
pysleep_zero_posix(void)
24362416
{
24372417
assert(!PyErr_Occurred());
24382418

2439-
int ret;
2419+
int ret, err;
2420+
Py_BEGIN_ALLOW_THREADS
2421+
#ifdef HAVE_CLOCK_NANOSLEEP
2422+
struct timespec zero = {0, 0};
2423+
ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &zero, NULL);
2424+
err = ret;
2425+
#elif defined(HAVE_NANOSLEEP)
2426+
struct timespec zero = {0, 0};
2427+
ret = nanosleep(&zero, NULL);
2428+
err = errno;
2429+
#else
24402430
// POSIX-compliant select(2) allows the 'timeout' parameter to
24412431
// be modified but also mandates that the function should return
24422432
// immediately if *both* structure's fields are zero (which is
@@ -2447,13 +2437,15 @@ pysleep_zero_posix(void)
24472437
// this is also the case for zero timeouts), we prefer supplying
24482438
// a fresh timeout everytime.
24492439
struct timeval zero = {0, 0};
2450-
Py_BEGIN_ALLOW_THREADS
24512440
ret = select(0, NULL, NULL, NULL, &zero);
2441+
err = errno;
2442+
#endif
24522443
Py_END_ALLOW_THREADS
24532444
if (ret == 0) {
24542445
return 0;
24552446
}
2456-
if (errno != EINTR) {
2447+
if (err != EINTR) {
2448+
errno = err;
24572449
PyErr_SetFromErrno(PyExc_OSError);
24582450
return -1;
24592451
}

0 commit comments

Comments
 (0)