Skip to content

Commit ac430e3

Browse files
authored
gh-158304: Avoid creating StopIteration for every value yielded by an async generator (#158315)
1 parent 5d88764 commit ac430e3

2 files changed

Lines changed: 48 additions & 23 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Speed up iteration over asynchronous generators: values yielded to
2+
``async for`` no longer create and discard a :exc:`StopIteration` exception
3+
at every nesting level. The ``async_generators`` pyperformance benchmark is
4+
about 30% faster. Patch by Shamil Abdulaev.

‎Objects/genobject.c‎

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,8 +1965,10 @@ PyAsyncGen_New(PyFrameObject *f, PyObject *name, PyObject *qualname)
19651965
return (PyObject*)ag;
19661966
}
19671967

1968-
static PyObject *
1969-
async_gen_unwrap_value(PyAsyncGenObject *gen, PyObject *result)
1968+
// Report an async yield as PYGEN_RETURN with the yielded value.
1969+
static PySendResult
1970+
async_gen_unwrap_send(PyAsyncGenObject *gen, PyObject *result,
1971+
PyObject **presult)
19701972
{
19711973
if (result == NULL) {
19721974
if (!PyErr_Occurred()) {
@@ -1979,17 +1981,31 @@ async_gen_unwrap_value(PyAsyncGenObject *gen, PyObject *result)
19791981
FT_ATOMIC_STORE_INT8_RELAXED(gen->ag_closed, 1);
19801982
}
19811983

1982-
return NULL;
1984+
*presult = NULL;
1985+
return PYGEN_ERROR;
19831986
}
19841987

19851988
if (_PyAsyncGenWrappedValue_CheckExact(result)) {
19861989
/* async yield */
1987-
_PyGen_SetStopIterationValue(((_PyAsyncGenWrappedValue*)result)->agw_val);
1990+
*presult = Py_NewRef(((_PyAsyncGenWrappedValue*)result)->agw_val);
19881991
Py_DECREF(result);
1989-
return NULL;
1992+
return PYGEN_RETURN;
19901993
}
19911994

1992-
return result;
1995+
*presult = result;
1996+
return PYGEN_NEXT;
1997+
}
1998+
1999+
static PyObject *
2000+
async_gen_unwrap_value(PyAsyncGenObject *gen, PyObject *result)
2001+
{
2002+
PyObject *value;
2003+
if (async_gen_unwrap_send(gen, result, &value) == PYGEN_RETURN) {
2004+
_PyGen_SetStopIterationValue(value);
2005+
Py_DECREF(value);
2006+
return NULL;
2007+
}
2008+
return value;
19932009
}
19942010

19952011

@@ -2002,7 +2018,10 @@ async_gen_asend_dealloc(PyObject *self)
20022018
assert(PyAsyncGenASend_CheckExact(self));
20032019
PyAsyncGenASend *ags = _PyAsyncGenASend_CAST(self);
20042020

2005-
if (PyObject_CallFinalizerFromDealloc(self)) {
2021+
// The finalizer only warns about an asend() that was never awaited.
2022+
if (ags->ags_state == AWAITABLE_STATE_INIT
2023+
&& PyObject_CallFinalizerFromDealloc(self))
2024+
{
20062025
return;
20072026
}
20082027

@@ -2025,18 +2044,19 @@ async_gen_asend_traverse(PyObject *self, visitproc visit, void *arg)
20252044
}
20262045

20272046

2028-
static PyObject *
2029-
async_gen_asend_send(PyObject *self, PyObject *arg)
2047+
PySendResult
2048+
_PyAsyncGenASend_Send(PyObject *self, PyObject *arg, PyObject **presult)
20302049
{
20312050
PyAsyncGenASend *o = _PyAsyncGenASend_CAST(self);
20322051

2052+
*presult = NULL;
20332053
int8_t state = FT_ATOMIC_LOAD_INT8_RELAXED(o->ags_state);
20342054
do {
20352055
if (state == AWAITABLE_STATE_CLOSED) {
20362056
PyErr_SetString(
20372057
PyExc_RuntimeError,
20382058
"cannot reuse already awaited __anext__()/asend()");
2039-
return NULL;
2059+
return PYGEN_ERROR;
20402060
}
20412061
if (state == AWAITABLE_STATE_ITER) {
20422062
goto do_send;
@@ -2053,37 +2073,37 @@ async_gen_asend_send(PyObject *self, PyObject *arg)
20532073
PyErr_SetString(
20542074
PyExc_RuntimeError,
20552075
"anext(): asynchronous generator is already running");
2056-
return NULL;
2076+
return PYGEN_ERROR;
20572077
}
20582078

20592079
if (arg == NULL || arg == Py_None) {
20602080
arg = o->ags_sendval;
20612081
}
20622082

20632083
PyObject *result;
2084+
PySendResult res;
20642085
do_send:
20652086
result = gen_send((PyObject*)o->ags_gen, arg);
2066-
result = async_gen_unwrap_value(o->ags_gen, result);
2087+
res = async_gen_unwrap_send(o->ags_gen, result, presult);
20672088

2068-
if (result == NULL) {
2089+
if (res != PYGEN_NEXT) {
20692090
FT_ATOMIC_STORE_INT8_RELAXED(o->ags_state, AWAITABLE_STATE_CLOSED);
20702091
FT_ATOMIC_STORE_INT8_RELEASE(o->ags_gen->ag_running_async, 0);
20712092
}
20722093

2073-
return result;
2094+
return res;
20742095
}
20752096

2076-
PySendResult
2077-
_PyAsyncGenASend_Send(PyObject *iter, PyObject *arg, PyObject **result)
2097+
static PyObject *
2098+
async_gen_asend_send(PyObject *self, PyObject *arg)
20782099
{
2079-
*result = async_gen_asend_send(iter, arg);
2080-
if (*result != NULL) {
2081-
return PYGEN_NEXT;
2082-
}
2083-
if (_PyGen_FetchStopIterationValue(result) == 0) {
2084-
return PYGEN_RETURN;
2100+
PyObject *result;
2101+
if (_PyAsyncGenASend_Send(self, arg, &result) == PYGEN_RETURN) {
2102+
_PyGen_SetStopIterationValue(result);
2103+
Py_DECREF(result);
2104+
return NULL;
20852105
}
2086-
return PYGEN_ERROR;
2106+
return result;
20872107
}
20882108

20892109

@@ -2168,6 +2188,7 @@ static void
21682188
async_gen_asend_finalize(PyObject *self)
21692189
{
21702190
PyAsyncGenASend *ags = _PyAsyncGenASend_CAST(self);
2191+
// async_gen_asend_dealloc() calls this only in the INIT state.
21712192
if (ags->ags_state == AWAITABLE_STATE_INIT) {
21722193
_PyErr_WarnUnawaitedAgenMethod(ags->ags_gen, &_Py_ID(asend));
21732194
}

0 commit comments

Comments
 (0)