diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-06-09-10-54-31.bpo-37207.bLjgLR.rst b/Misc/NEWS.d/next/Core and Builtins/2019-06-09-10-54-31.bpo-37207.bLjgLR.rst new file mode 100644 index 00000000000000..cb01e8ba0e9628 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-06-09-10-54-31.bpo-37207.bLjgLR.rst @@ -0,0 +1,2 @@ +Speed up calls to ``range()``, ``list()`` and ``dict()`` by about 30%, by +using the PEP 590 ``vectorcall`` calling convention. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 73956df4ab2a5b..eb5086088d6c36 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -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); + 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) { @@ -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 * diff --git a/Objects/listobject.c b/Objects/listobject.c index 645742b801fac4..6d8d15506e61ce 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -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__ @@ -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 **************************/ diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index 239ace6f4235ed..83fd6f9bcb36d0 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -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\ @@ -702,6 +762,7 @@ PyTypeObject PyRange_Type = { 0, /* tp_init */ 0, /* tp_alloc */ range_new, /* tp_new */ + .tp_vectorcall = (vectorcallfunc)range_vectorcall }; /*********************** range Iterator **************************/ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 1d8216d2988728..7ebf77db71fe4a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -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__ */ + if (Py_TYPE(type)->tp_call == PyType_Type.tp_call) { + type->tp_vectorcall = base->tp_vectorcall; + } } COPYSLOT(tp_call); }