From c01d5b5b6144ce2fdb602886cd999b995945c224 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 1 Apr 2019 17:58:49 +0100 Subject: [PATCH 01/10] Use vector call for range(). --- Objects/rangeobject.c | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index 239ace6f4235ed..62e7f5d3a4e1d1 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -121,6 +121,67 @@ 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()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: + step = validate_step(step); /* Caution, this can clear exceptions */ + /* 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; + } + if (!step) { + Py_DECREF(start); + Py_DECREF(stop); + return NULL; + } + break; + default: + PyErr_Format(PyExc_TypeError, "range() expected at most 3 arguments, got %zd", 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 +763,7 @@ PyTypeObject PyRange_Type = { 0, /* tp_init */ 0, /* tp_alloc */ range_new, /* tp_new */ + .tp_vectorcall = range_vectorcall }; /*********************** range Iterator **************************/ From 6075fc1c01759b69a5022dad4a3b4223003382b0 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 2 Apr 2019 20:11:06 +0100 Subject: [PATCH 02/10] Use vector call for list() --- Objects/listobject.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Objects/listobject.c b/Objects/listobject.c index 645742b801fac4..bc4b16633a60c0 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -2726,6 +2726,35 @@ 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 one argument, got %zd", 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 +3070,7 @@ PyTypeObject PyList_Type = { PyType_GenericAlloc, /* tp_alloc */ PyType_GenericNew, /* tp_new */ PyObject_GC_Del, /* tp_free */ + .tp_vectorcall = list_vectorcall, }; /*********************** List Iterator **************************/ From 9d86ea5c53dd48984201cb4c65a9f59edd44daad Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sat, 20 Apr 2019 19:07:05 +0100 Subject: [PATCH 03/10] Use vector call for dict() --- Objects/dictobject.c | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 73956df4ab2a5b..7e7fd1d07f2c77 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3286,6 +3286,56 @@ 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 one argument, got %zd", 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 += 1; + } + 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 +3394,7 @@ PyTypeObject PyDict_Type = { PyType_GenericAlloc, /* tp_alloc */ dict_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ + .tp_vectorcall = dict_vectorcall, }; PyObject * From 20eea929edd39787e22e5426111f97e35f4f7541 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sun, 9 Jun 2019 10:55:02 +0100 Subject: [PATCH 04/10] Add news entry. --- .../Core and Builtins/2019-06-09-10-54-31.bpo-37207.bLjgLR.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-06-09-10-54-31.bpo-37207.bLjgLR.rst 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. From 821af67ca05e6cedf426dc13da15eb402c86153c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 13 Jun 2019 22:32:05 +0100 Subject: [PATCH 05/10] Fix up error messages in list/dict/range vectorcall implements to match previous error messages. --- Objects/dictobject.c | 2 +- Objects/listobject.c | 2 +- Objects/rangeobject.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 7e7fd1d07f2c77..ff30a5fc63fa9e 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3293,7 +3293,7 @@ dict_vectorcall( { size_t nargs = PyVectorcall_NARGS(nargsf); if (nargs > 1) { - PyErr_Format(PyExc_TypeError, "dict() expected at most one argument, got %zd", nargs); + PyErr_Format(PyExc_TypeError, "dict() expected at most 1 argument, got %zu", nargs); return NULL; } assert(PyType_Check(type)); diff --git a/Objects/listobject.c b/Objects/listobject.c index bc4b16633a60c0..0afebb7830e13c 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -2737,7 +2737,7 @@ list_vectorcall( } size_t nargs = PyVectorcall_NARGS(nargsf); if (nargs > 1) { - PyErr_Format(PyExc_TypeError, "list() expected at most one argument, got %zd", nargs); + PyErr_Format(PyExc_TypeError, "list() expected at most 1 argument, got %zu", nargs); return NULL; } diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index 62e7f5d3a4e1d1..d98cb06d429891 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -136,7 +136,7 @@ range_vectorcall( } switch(nargs) { case 0: - PyErr_Format(PyExc_TypeError, "range()range expected 1 arguments, got 0"); + PyErr_Format(PyExc_TypeError, "range() expected 1 arguments, got 0"); return NULL; case 1: stop = PyNumber_Index(args[0]); @@ -168,7 +168,7 @@ range_vectorcall( } break; default: - PyErr_Format(PyExc_TypeError, "range() expected at most 3 arguments, got %zd", nargs); + PyErr_Format(PyExc_TypeError, "range() expected at most 3 arguments, got %zu", nargs); return NULL; } obj = make_range_object(type, start, stop, step); From 0049db81f2dca06b116930a72ce6bc937d34c1aa Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 13 Jun 2019 22:44:34 +0100 Subject: [PATCH 06/10] Fix refcount leak in range_vectorcall. --- Objects/rangeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index d98cb06d429891..f685a18caea203 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -151,7 +151,6 @@ range_vectorcall( step = args[2]; //Intentional fall through case 2: - step = validate_step(step); /* Caution, this can clear exceptions */ /* Convert borrowed refs to owned refs */ start = PyNumber_Index(args[0]); if (!start) @@ -161,6 +160,7 @@ range_vectorcall( Py_DECREF(start); return NULL; } + step = validate_step(step); /* Caution, this can clear exceptions */ if (!step) { Py_DECREF(start); Py_DECREF(stop); From 8d4a2507e9bb80fa4bdd7c33629c594c6e8d7eb5 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 14 Jun 2019 09:09:09 +0100 Subject: [PATCH 07/10] Reformat vectorcall functions to conform to standard coding style. --- Objects/dictobject.c | 5 ++--- Objects/listobject.c | 5 ++--- Objects/rangeobject.c | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index ff30a5fc63fa9e..2a18fafbf62fb9 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3287,9 +3287,8 @@ dict_init(PyObject *self, PyObject *args, PyObject *kwds) } static PyObject * -dict_vectorcall( - PyObject *type, PyObject * const*args, - size_t nargsf, PyObject *kwnames) +dict_vectorcall(PyObject *type, PyObject * const*args, + size_t nargsf, PyObject *kwnames) { size_t nargs = PyVectorcall_NARGS(nargsf); if (nargs > 1) { diff --git a/Objects/listobject.c b/Objects/listobject.c index 0afebb7830e13c..6d8d15506e61ce 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -2727,9 +2727,8 @@ list___init___impl(PyListObject *self, PyObject *iterable) } static PyObject * -list_vectorcall( - PyObject *type, PyObject * const*args, - size_t nargsf, PyObject *kwnames) +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"); diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index f685a18caea203..5f59d628923f5b 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -123,9 +123,8 @@ range_new(PyTypeObject *type, PyObject *args, PyObject *kw) static PyObject * -range_vectorcall( - PyTypeObject *type, PyObject *const *args, - size_t nargsf, PyObject *kwnames) +range_vectorcall(PyTypeObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) { rangeobject *obj; size_t nargs = PyVectorcall_NARGS(nargsf); From 989a3d7226f9a5b5e1b8565c8fa215b662c45019 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sun, 23 Jun 2019 10:23:51 +0100 Subject: [PATCH 08/10] Minor formatting changes. --- Objects/dictobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 2a18fafbf62fb9..eb5086088d6c36 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3319,13 +3319,13 @@ dict_vectorcall(PyObject *type, PyObject * const*args, Py_DECREF(self); return NULL; } - args += 1; + args++; } if (kwnames == NULL) { return self; } Py_ssize_t items = PyTuple_GET_SIZE(kwnames); - for(Py_ssize_t index = 0; index < items; index++) { + 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); From 525ff6cd443bbdfafd8530597247359faa69b06b Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sun, 4 Aug 2019 09:37:40 +0100 Subject: [PATCH 09/10] Inherit tp_vectorcall when safe to do so. --- Objects/typeobject.c | 5 +++++ 1 file changed, 5 insertions(+) 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); } From c31914168b7c0e946ed3fe13f78adc75a36090ea Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 12 Sep 2019 10:11:03 +0100 Subject: [PATCH 10/10] Fix compiler warning. --- Objects/rangeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index 5f59d628923f5b..83fd6f9bcb36d0 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -762,7 +762,7 @@ PyTypeObject PyRange_Type = { 0, /* tp_init */ 0, /* tp_alloc */ range_new, /* tp_new */ - .tp_vectorcall = range_vectorcall + .tp_vectorcall = (vectorcallfunc)range_vectorcall }; /*********************** range Iterator **************************/