[Python-Dev] performance of generator termination (was: Re: problem with recursive "yield from" delegation)
Stefan Behnel
stefan_ml at behnel.de
Fri Mar 9 22:25:57 CET 2012
Antoine Pitrou, 08.03.2012 21:36:
> On Thu, 8 Mar 2012 14:36:06 -0600
> Benjamin Peterson wrote:
>> 2012/3/8 Stefan Behnel:
>>> Would that be acceptable for CPython as well or would you prefer full
>>> fledged normalisation?
>>
>> I think we have to normalize for correctness. Consider that it may be
>> some StopIteration subclass which set "value" on construction.
>
> Perhaps it would be time to drop the whole delayed normalization thing,
> provided the benchmarks don't exhibit a slowdown?
At least for Cython, always normalising the exception can make quite a
difference. For the nqueens benchmark, which uses short running generator
expressions (not affected by this particular change), a quick hack to
fetch, normalise and restore the StopIteration exception raised at the end
of the generator expression run reduces the performance by 10% for me. I'd
expect code similar to the group item iterator in itertools.groupby() to
suffer even worse for very small groups.
A while ago, I wouldn't have expected generator termination to have that an
impact, but when we dropped the single frame+traceback creation at the end
of the generator run in Cython, that boosted the performance of the
compiled nqueens benchmark by 70% and a compiled Python version of
itertools.groupby() ran twice as fast as before. These things can make an
impressively large difference.
http://thread.gmane.org/gmane.comp.python.cython.devel/12993/focus=13044
I'm using the following in Cython now. Note how complex the pre-3.3 case
is, I'm sure that makes it even more worth the special case in older
CPython versions (including 2.x).
"""
static int __Pyx_PyGen_FetchStopIterationValue(PyObject **pvalue) {
PyObject *et, *ev, *tb;
PyObject *value = NULL;
if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
PyErr_Fetch(&et, &ev, &tb);
// most common case: plain StopIteration without argument
if (et == PyExc_StopIteration) {
if (!ev || !PyObject_IsInstance(ev, PyExc_StopIteration)) {
// PyErr_SetObject() puts the value directly into ev
if (!ev) {
Py_INCREF(Py_None);
ev = Py_None;
}
Py_XDECREF(tb);
Py_DECREF(et);
*pvalue = ev;
return 0;
}
}
// otherwise: normalise and check what that gives us
PyErr_NormalizeException(&et, &ev, &tb);
if (PyObject_IsInstance(ev, PyExc_StopIteration)) {
Py_XDECREF(tb);
Py_DECREF(et);
#if PY_VERSION_HEX >= 0x030300A0
value = ((PyStopIterationObject *)ev)->value;
Py_INCREF(value);
Py_DECREF(ev);
#else
PyObject* args = PyObject_GetAttrString(ev, "args");
Py_DECREF(ev);
if (args) {
value = PyObject_GetItem(args, 0);
Py_DECREF(args);
}
if (!value)
PyErr_Clear();
#endif
} else {
// looks like normalisation failed - raise the new exception
PyErr_Restore(et, ev, tb);
return -1;
}
} else if (PyErr_Occurred()) {
return -1;
}
if (value == NULL) {
Py_INCREF(Py_None);
value = Py_None;
}
*pvalue = value;
return 0;
}
"""
Stefan
More information about the Python-Dev
mailing list