Skip to content

bpo-37207: Use PEP 590 vectorcall to speed up range(), list() and dict() by about 30% #13930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Speed up calls to ``range()``, ``list()`` and ``dict()`` by about 30%, by
using the PEP 590 ``vectorcall`` calling convention.
50 changes: 50 additions & 0 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3286,6 +3286,55 @@ dict_init(PyObject *self, PyObject *args, PyObject *kwds)
return dict_update_common(self, args, kwds, "dict");
}

static PyObject *
dict_vectorcall(PyObject *type, PyObject * const*args,
size_t nargsf, PyObject *kwnames)
{
size_t nargs = PyVectorcall_NARGS(nargsf);
if (nargs > 1) {
PyErr_Format(PyExc_TypeError, "dict() expected at most 1 argument, got %zu", nargs);
return NULL;
}
assert(PyType_Check(type));
PyObject *self = dict_new((PyTypeObject *)type, NULL, NULL);
if (self == NULL) {
return NULL;
}
if (nargs == 1) {
int err = 0;
PyObject *arg = args[0];
_Py_IDENTIFIER(keys);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like two same identifiers in single source file.
Please make _Py_IDENTIFIER(keys); global static variable.

PyObject *func;
if (_PyObject_LookupAttrId(arg, &PyId_keys, &func) < 0) {
err = -1;
}
else if (func != NULL) {
Py_DECREF(func);
err = PyDict_Merge(self, arg, 1);
}
else {
err = PyDict_MergeFromSeq2(self, arg, 1);
}
if (err < 0) {
Py_DECREF(self);
return NULL;
}
args++;
}
if (kwnames == NULL) {
return self;
}
Py_ssize_t items = PyTuple_GET_SIZE(kwnames);
for (Py_ssize_t index = 0; index < items; index++) {
int err = PyDict_SetItem(self, PyTuple_GET_ITEM(kwnames, index), args[index]);
if (err < 0) {
Py_DECREF(self);
return NULL;
}
}
return self;
}

static PyObject *
dict_iter(PyDictObject *dict)
{
Expand Down Expand Up @@ -3344,6 +3393,7 @@ PyTypeObject PyDict_Type = {
PyType_GenericAlloc, /* tp_alloc */
dict_new, /* tp_new */
PyObject_GC_Del, /* tp_free */
.tp_vectorcall = dict_vectorcall,
};

PyObject *
Expand Down
29 changes: 29 additions & 0 deletions Objects/listobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2726,6 +2726,34 @@ list___init___impl(PyListObject *self, PyObject *iterable)
return 0;
}

static PyObject *
list_vectorcall(PyObject *type, PyObject * const*args,
size_t nargsf, PyObject *kwnames)
{
if (kwnames && PyTuple_GET_SIZE(kwnames) != 0) {
PyErr_Format(PyExc_TypeError, "list() takes no keyword arguments");
return NULL;
}
size_t nargs = PyVectorcall_NARGS(nargsf);
if (nargs > 1) {
PyErr_Format(PyExc_TypeError, "list() expected at most 1 argument, got %zu", nargs);
return NULL;
}

assert(PyType_Check(type));
PyObject *list = PyType_GenericAlloc((PyTypeObject *)type, 0);
if (list == NULL) {
return NULL;
}
PyObject *iterator = nargs ? args[0] : NULL;
if (list___init___impl((PyListObject *)list, iterator)) {
Py_DECREF(list);
return NULL;
}
return list;
}


/*[clinic input]
list.__sizeof__

Expand Down Expand Up @@ -3041,6 +3069,7 @@ PyTypeObject PyList_Type = {
PyType_GenericAlloc, /* tp_alloc */
PyType_GenericNew, /* tp_new */
PyObject_GC_Del, /* tp_free */
.tp_vectorcall = list_vectorcall,
};

/*********************** List Iterator **************************/
Expand Down
61 changes: 61 additions & 0 deletions Objects/rangeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,66 @@ range_new(PyTypeObject *type, PyObject *args, PyObject *kw)
return NULL;
}


static PyObject *
range_vectorcall(PyTypeObject *type, PyObject *const *args,
size_t nargsf, PyObject *kwnames)
{
rangeobject *obj;
size_t nargs = PyVectorcall_NARGS(nargsf);
PyObject *start = NULL, *stop = NULL, *step = NULL;
if (kwnames && PyTuple_GET_SIZE(kwnames) != 0) {
PyErr_Format(PyExc_TypeError, "range() takes no keyword arguments");
return NULL;
}
switch(nargs) {
case 0:
PyErr_Format(PyExc_TypeError, "range() expected 1 arguments, got 0");
return NULL;
case 1:
stop = PyNumber_Index(args[0]);
if (!stop)
return NULL;
Py_INCREF(_PyLong_Zero);
start = _PyLong_Zero;
Py_INCREF(_PyLong_One);
step = _PyLong_One;
break;
case 3:
step = args[2];
//Intentional fall through
case 2:
/* Convert borrowed refs to owned refs */
start = PyNumber_Index(args[0]);
if (!start)
return NULL;
stop = PyNumber_Index(args[1]);
if (!stop) {
Py_DECREF(start);
return NULL;
}
step = validate_step(step); /* Caution, this can clear exceptions */
if (!step) {
Py_DECREF(start);
Py_DECREF(stop);
return NULL;
}
break;
default:
PyErr_Format(PyExc_TypeError, "range() expected at most 3 arguments, got %zu", nargs);
return NULL;
}
obj = make_range_object(type, start, stop, step);
if (obj != NULL)
return (PyObject *) obj;

/* Failed to create object, release attributes */
Py_DECREF(start);
Py_DECREF(stop);
Py_DECREF(step);
return NULL;
}

PyDoc_STRVAR(range_doc,
"range(stop) -> range object\n\
range(start, stop[, step]) -> range object\n\
Expand Down Expand Up @@ -702,6 +762,7 @@ PyTypeObject PyRange_Type = {
0, /* tp_init */
0, /* tp_alloc */
range_new, /* tp_new */
.tp_vectorcall = (vectorcallfunc)range_vectorcall
};

/*********************** range Iterator **************************/
Expand Down
5 changes: 5 additions & 0 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -5170,6 +5170,11 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
!(type->tp_flags & Py_TPFLAGS_HEAPTYPE))
{
type->tp_flags |= _Py_TPFLAGS_HAVE_VECTORCALL;
/* Also inherit tp_vectorcall if tp_call of the
* metaclass is type.__call__ */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* metaclass is type.__call__ */
* metaclass is type.__call__ */

if (Py_TYPE(type)->tp_call == PyType_Type.tp_call) {
type->tp_vectorcall = base->tp_vectorcall;
}
}
COPYSLOT(tp_call);
}
Expand Down