From a4134f3f75d8472c9a23fb771c6831205d42dbd8 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Tue, 27 Oct 2026 17:20:05 -0700 Subject: [PATCH] gh-87613: Use AC vectorcall for filter Move filter.__new__ to Argument Clinic with @vectorcall, dropping the hand-written filter_new and filter_vectorcall. The generated vectorcall name matches the hand-written one so the tp_vectorcall slot is unchanged, and argument error messages are unchanged on both paths. pyperf microbenchmark (PGO+LTO, run serially), no significant change on the vectorcall path: | Benchmark | base | filter | |---------------------|:-------:|:---------------------:| | `filter.__new__` | 45.4 ns | 42.7 ns: 1.06x faster | | filter(bool, list) | 32.3 ns | 32.8 ns: 1.01x slower | | filter(None, range) | 30.6 ns | 31.8 ns: 1.04x slower | | Geometric mean | (ref) | 1.01x faster | Benchmark hidden because not significant (3): subclass(None, tuple), filter(None, tuple), list(filter(None, list)) filter.__new__ drops PyArg_UnpackTuple for inline clinic parsing. Co-Authored-By: Claude Opus 5.5 --- Python/bltinmodule.c | 72 +++++++++-------------------------- Python/clinic/bltinmodule.c.h | 63 +++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 55 deletions(-) diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 40471af7186ae3f..f09ae32021cbd17 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -566,65 +566,37 @@ typedef struct { #define _filterobject_CAST(op) ((filterobject *)(op)) -static PyObject * -filter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyObject *func, *seq; - PyObject *it; - filterobject *lz; - - if ((type == &PyFilter_Type || type->tp_init == PyFilter_Type.tp_init) && - !_PyArg_NoKeywords("filter", kwds)) - return NULL; - - if (!PyArg_UnpackTuple(args, "filter", 2, 2, &func, &seq)) - return NULL; - - /* Get iterator. */ - it = PyObject_GetIter(seq); - if (it == NULL) - return NULL; +/*[clinic input] +class filter "filterobject *" "&PyFilter_Type" +@vectorcall +@classmethod +filter.__new__ as filter_new - /* create filterobject structure */ - lz = (filterobject *)type->tp_alloc(type, 0); - if (lz == NULL) { - Py_DECREF(it); - return NULL; - } + function as func: object + iterable as seq: object + / - lz->func = Py_NewRef(func); - lz->it = it; +Return an iterator over items of iterable where function(item) is true. - return (PyObject *)lz; -} +If function is None, return the items that are true. +[clinic start generated code]*/ static PyObject * -filter_vectorcall(PyObject *type, PyObject * const*args, - size_t nargsf, PyObject *kwnames) +filter_new_impl(PyTypeObject *type, PyObject *func, PyObject *seq) +/*[clinic end generated code: output=2134a9f0032a6037 input=57836123a59c4d91]*/ { - PyTypeObject *tp = _PyType_CAST(type); - if (tp == &PyFilter_Type && !_PyArg_NoKwnames("filter", kwnames)) { - return NULL; - } - - Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); - if (!_PyArg_CheckPositional("filter", nargs, 2, 2)) { - return NULL; - } - - PyObject *it = PyObject_GetIter(args[1]); + PyObject *it = PyObject_GetIter(seq); if (it == NULL) { return NULL; } - filterobject *lz = (filterobject *)tp->tp_alloc(tp, 0); - + filterobject *lz = (filterobject *)type->tp_alloc(type, 0); if (lz == NULL) { Py_DECREF(it); return NULL; } - lz->func = Py_NewRef(args[0]); + lz->func = Py_NewRef(func); lz->it = it; return (PyObject *)lz; @@ -699,14 +671,6 @@ static PyMethodDef filter_methods[] = { {NULL, NULL} /* sentinel */ }; -PyDoc_STRVAR(filter_doc, -"filter(function, iterable, /)\n\ ---\n\ -\n\ -Return an iterator yielding those items of iterable for which\n\ -function(item) is true. If function is None, return the items that\n\ -are true."); - PyTypeObject PyFilter_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "filter", /* tp_name */ @@ -730,7 +694,7 @@ PyTypeObject PyFilter_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ - filter_doc, /* tp_doc */ + filter_new__doc__, /* tp_doc */ filter_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ @@ -749,7 +713,7 @@ PyTypeObject PyFilter_Type = { PyType_GenericAlloc, /* tp_alloc */ filter_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ - .tp_vectorcall = filter_vectorcall + .tp_vectorcall = filter_vectorcall, }; diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index bdb3dd560b39b8b..18a6576f43f69cf 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -279,6 +279,67 @@ PyDoc_STRVAR(builtin_callable__doc__, #define BUILTIN_CALLABLE_METHODDEF \ {"callable", (PyCFunction)builtin_callable, METH_O, builtin_callable__doc__}, +PyDoc_STRVAR(filter_new__doc__, +"filter(function, iterable, /)\n" +"--\n" +"\n" +"Return an iterator over items of iterable where function(item) is true.\n" +"\n" +"If function is None, return the items that are true."); + +static PyObject * +filter_new_impl(PyTypeObject *type, PyObject *func, PyObject *seq); + +static PyObject * +filter_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyTypeObject *base_tp = &PyFilter_Type; + PyObject *func; + PyObject *seq; + + if ((type == base_tp || type->tp_init == base_tp->tp_init) && + !_PyArg_NoKeywords("filter", kwargs)) { + goto exit; + } + if (!_PyArg_CheckPositional("filter", PyTuple_GET_SIZE(args), 2, 2)) { + goto exit; + } + func = PyTuple_GET_ITEM(args, 0); + seq = PyTuple_GET_ITEM(args, 1); + return_value = filter_new_impl(type, func, seq); + +exit: + return return_value; +} + +static PyObject * +filter_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + PyObject *return_value = NULL; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *func; + PyObject *seq; + + assert(_PyType_CAST(type)->tp_new == filter_new); + /* Make sure the type object is immutable: the generated + * vectorcall doesn't deal e.g. with users reassigning __init__. */ + assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); + if (!_PyArg_NoKwnames("filter", kwnames)) { + goto exit; + } + if (!_PyArg_CheckPositional("filter", nargs, 2, 2)) { + goto exit; + } + func = args[0]; + seq = args[1]; + return_value = filter_new_impl(_PyType_CAST(type), func, seq); + +exit: + return return_value; +} + PyDoc_STRVAR(builtin_format__doc__, "format($module, value, format_spec=\'\', /)\n" "--\n" @@ -1495,4 +1556,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=1dacff294aaa7485 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e42f21f39d16c160 input=a9049054013a1b77]*/