[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

AltStyle によって変換されたページ (->オリジナル) /