Skip to content

Commit 9d411c1

Browse files
authored
bpo-32296: Make get_running_loop() another 4-5x faster (#5277)
1 parent e768c86 commit 9d411c1

File tree

1 file changed

+102
-62
lines changed

1 file changed

+102
-62
lines changed

Modules/_asynciomodule.c

Lines changed: 102 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ _Py_IDENTIFIER(_wakeup);
2626
/* State of the _asyncio module */
2727
static PyObject *asyncio_mod;
2828
static PyObject *inspect_isgenerator;
29-
static PyObject *os_getpid;
3029
static PyObject *traceback_extract_stack;
3130
static PyObject *asyncio_get_event_loop_policy;
3231
static PyObject *asyncio_future_repr_info_func;
@@ -38,6 +37,9 @@ static PyObject *asyncio_InvalidStateError;
3837
static PyObject *asyncio_CancelledError;
3938
static PyObject *context_kwname;
4039

40+
static PyObject *cached_running_holder;
41+
static volatile uint64_t cached_running_holder_tsid;
42+
4143

4244
/* WeakSet containing all alive tasks. */
4345
static PyObject *all_tasks;
@@ -95,9 +97,18 @@ typedef struct {
9597
TaskObj *ww_task;
9698
} TaskWakeupMethWrapper;
9799

100+
typedef struct {
101+
PyObject_HEAD
102+
PyObject *rl_loop;
103+
#if defined(HAVE_GETPID) && !defined(MS_WINDOWS)
104+
pid_t rl_pid;
105+
#endif
106+
} PyRunningLoopHolder;
107+
98108

99109
static PyTypeObject FutureType;
100110
static PyTypeObject TaskType;
111+
static PyTypeObject PyRunningLoopHolder_Type;
101112

102113

103114
#define Future_CheckExact(obj) (Py_TYPE(obj) == &FutureType)
@@ -116,9 +127,11 @@ class _asyncio.Future "FutureObj *" "&Future_Type"
116127

117128

118129
/* Get FutureIter from Future */
119-
static PyObject* future_new_iter(PyObject *);
130+
static PyObject * future_new_iter(PyObject *);
120131
static inline int future_call_schedule_callbacks(FutureObj *);
121132

133+
static PyRunningLoopHolder * new_running_loop_holder(PyObject *);
134+
122135

123136
static int
124137
_is_coroutine(PyObject *coro)
@@ -214,60 +227,57 @@ get_future_loop(PyObject *fut)
214227
static int
215228
get_running_loop(PyObject **loop)
216229
{
217-
PyObject *ts_dict;
218-
PyObject *running_tuple;
219-
PyObject *running_loop;
220-
PyObject *running_loop_pid;
221-
PyObject *current_pid;
222-
int same_pid;
230+
PyObject *rl;
223231

224-
ts_dict = PyThreadState_GetDict(); // borrowed
225-
if (ts_dict == NULL) {
226-
PyErr_SetString(
227-
PyExc_RuntimeError, "thread-local storage is not available");
228-
goto error;
232+
PyThreadState *ts = PyThreadState_Get();
233+
if (ts->id == cached_running_holder_tsid && cached_running_holder != NULL) {
234+
// Fast path, check the cache.
235+
rl = cached_running_holder; // borrowed
229236
}
237+
else {
238+
if (ts->dict == NULL) {
239+
goto not_found;
240+
}
230241

231-
running_tuple = _PyDict_GetItemId(
232-
ts_dict, &PyId___asyncio_running_event_loop__); // borrowed
233-
if (running_tuple == NULL) {
234-
/* _PyDict_GetItemId doesn't set an error if key is not found */
235-
goto not_found;
242+
rl = _PyDict_GetItemIdWithError(
243+
ts->dict, &PyId___asyncio_running_event_loop__); // borrowed
244+
if (rl == NULL) {
245+
if (PyErr_Occurred()) {
246+
goto error;
247+
}
248+
else {
249+
goto not_found;
250+
}
251+
}
252+
253+
cached_running_holder = rl; // borrowed
254+
cached_running_holder_tsid = ts->id;
236255
}
237256

238-
assert(PyTuple_CheckExact(running_tuple));
239-
assert(PyTuple_Size(running_tuple) == 2);
240-
running_loop = PyTuple_GET_ITEM(running_tuple, 0); // borrowed
241-
running_loop_pid = PyTuple_GET_ITEM(running_tuple, 1); // borrowed
257+
assert(Py_TYPE(rl) == &PyRunningLoopHolder_Type);
258+
PyObject *running_loop = ((PyRunningLoopHolder *)rl)->rl_loop;
242259

243260
if (running_loop == Py_None) {
244261
goto not_found;
245262
}
246263

247-
current_pid = _PyObject_CallNoArg(os_getpid);
248-
if (current_pid == NULL) {
249-
goto error;
250-
}
251-
same_pid = PyObject_RichCompareBool(current_pid, running_loop_pid, Py_EQ);
252-
Py_DECREF(current_pid);
253-
if (same_pid == -1) {
254-
goto error;
264+
#if defined(HAVE_GETPID) && !defined(MS_WINDOWS)
265+
/* On Windows there is no getpid, but there is also no os.fork(),
266+
so there is no need for this check.
267+
*/
268+
if (getpid() != ((PyRunningLoopHolder *)rl)->rl_pid) {
269+
goto not_found;
255270
}
271+
#endif
256272

257-
if (same_pid) {
258-
// current_pid == running_loop_pid
259-
goto found;
260-
}
273+
Py_INCREF(running_loop);
274+
*loop = running_loop;
275+
return 0;
261276

262277
not_found:
263278
*loop = NULL;
264279
return 0;
265280

266-
found:
267-
Py_INCREF(running_loop);
268-
*loop = running_loop;
269-
return 0;
270-
271281
error:
272282
*loop = NULL;
273283
return -1;
@@ -277,38 +287,28 @@ get_running_loop(PyObject **loop)
277287
static int
278288
set_running_loop(PyObject *loop)
279289
{
280-
PyObject *ts_dict;
281-
PyObject *running_tuple;
282-
PyObject *current_pid;
290+
cached_running_holder = NULL;
291+
cached_running_holder_tsid = 0;
283292

284-
ts_dict = PyThreadState_GetDict(); // borrowed
293+
PyObject *ts_dict = PyThreadState_GetDict(); // borrowed
285294
if (ts_dict == NULL) {
286295
PyErr_SetString(
287296
PyExc_RuntimeError, "thread-local storage is not available");
288297
return -1;
289298
}
290299

291-
current_pid = _PyObject_CallNoArg(os_getpid);
292-
if (current_pid == NULL) {
300+
PyRunningLoopHolder *rl = new_running_loop_holder(loop);
301+
if (rl == NULL) {
293302
return -1;
294303
}
295304

296-
running_tuple = PyTuple_New(2);
297-
if (running_tuple == NULL) {
298-
Py_DECREF(current_pid);
299-
return -1;
300-
}
301-
302-
Py_INCREF(loop);
303-
PyTuple_SET_ITEM(running_tuple, 0, loop);
304-
PyTuple_SET_ITEM(running_tuple, 1, current_pid); // borrowed
305-
306305
if (_PyDict_SetItemId(
307-
ts_dict, &PyId___asyncio_running_event_loop__, running_tuple)) {
308-
Py_DECREF(running_tuple); // will cleanup loop & current_pid
306+
ts_dict, &PyId___asyncio_running_event_loop__, (PyObject *)rl) < 0)
307+
{
308+
Py_DECREF(rl); // will cleanup loop & current_pid
309309
return -1;
310310
}
311-
Py_DECREF(running_tuple);
311+
Py_DECREF(rl);
312312

313313
return 0;
314314
}
@@ -3184,6 +3184,47 @@ _asyncio__leave_task_impl(PyObject *module, PyObject *loop, PyObject *task)
31843184
}
31853185

31863186

3187+
/*********************** PyRunningLoopHolder ********************/
3188+
3189+
3190+
static PyRunningLoopHolder *
3191+
new_running_loop_holder(PyObject *loop)
3192+
{
3193+
PyRunningLoopHolder *rl = PyObject_New(
3194+
PyRunningLoopHolder, &PyRunningLoopHolder_Type);
3195+
if (rl == NULL) {
3196+
return NULL;
3197+
}
3198+
3199+
#if defined(HAVE_GETPID) && !defined(MS_WINDOWS)
3200+
rl->rl_pid = getpid();
3201+
#endif
3202+
3203+
Py_INCREF(loop);
3204+
rl->rl_loop = loop;
3205+
3206+
return rl;
3207+
}
3208+
3209+
3210+
static void
3211+
PyRunningLoopHolder_tp_dealloc(PyRunningLoopHolder *rl)
3212+
{
3213+
Py_CLEAR(rl->rl_loop);
3214+
PyObject_Free(rl);
3215+
}
3216+
3217+
3218+
static PyTypeObject PyRunningLoopHolder_Type = {
3219+
PyVarObject_HEAD_INIT(NULL, 0)
3220+
"_RunningLoopHolder",
3221+
sizeof(PyRunningLoopHolder),
3222+
.tp_getattro = PyObject_GenericGetAttr,
3223+
.tp_flags = Py_TPFLAGS_DEFAULT,
3224+
.tp_dealloc = (destructor)PyRunningLoopHolder_tp_dealloc,
3225+
};
3226+
3227+
31873228
/*********************** Module **************************/
31883229

31893230

@@ -3212,7 +3253,6 @@ module_free(void *m)
32123253
{
32133254
Py_CLEAR(asyncio_mod);
32143255
Py_CLEAR(inspect_isgenerator);
3215-
Py_CLEAR(os_getpid);
32163256
Py_CLEAR(traceback_extract_stack);
32173257
Py_CLEAR(asyncio_future_repr_info_func);
32183258
Py_CLEAR(asyncio_get_event_loop_policy);
@@ -3295,9 +3335,6 @@ module_init(void)
32953335
WITH_MOD("inspect")
32963336
GET_MOD_ATTR(inspect_isgenerator, "isgenerator")
32973337

3298-
WITH_MOD("os")
3299-
GET_MOD_ATTR(os_getpid, "getpid")
3300-
33013338
WITH_MOD("traceback")
33023339
GET_MOD_ATTR(traceback_extract_stack, "extract_stack")
33033340

@@ -3370,6 +3407,9 @@ PyInit__asyncio(void)
33703407
if (PyType_Ready(&TaskType) < 0) {
33713408
return NULL;
33723409
}
3410+
if (PyType_Ready(&PyRunningLoopHolder_Type) < 0) {
3411+
return NULL;
3412+
}
33733413

33743414
PyObject *m = PyModule_Create(&_asynciomodule);
33753415
if (m == NULL) {

0 commit comments

Comments
 (0)