From b4f32714c6140ef3ee3e15367760cf358e7316fb Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 1 Apr 2019 17:58:49 +0100 Subject: [PATCH 1/5] Use vectorcall for range() --- Objects/rangeobject.c | 61 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index 343a80c76b0bce..c75818449f94cd 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -138,6 +138,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\ @@ -719,6 +779,7 @@ PyTypeObject PyRange_Type = { 0, /* tp_init */ 0, /* tp_alloc */ range_new, /* tp_new */ + .tp_vectorcall = (vectorcallfunc)range_vectorcall }; /*********************** range Iterator **************************/ From 6d8063baab2623b14196553457a0bc473dc6d69f Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 11 Feb 2020 14:38:59 +0100 Subject: [PATCH 2/5] 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..c20d48a493c348 --- /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()`` by about 30%, by using the +PEP 590 ``vectorcall`` calling convention. Patch by Mark Shannon. From 804e18c1bc5f525095f131d3cbc6a9873b7b692d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 11 Feb 2020 14:44:01 +0100 Subject: [PATCH 3/5] Unify implementations of range_new and range_vectorcall --- Objects/rangeobject.c | 87 +++++++++++-------------------------------- 1 file changed, 21 insertions(+), 66 deletions(-) diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index c75818449f94cd..1fe69ddba62931 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -2,6 +2,7 @@ #include "Python.h" #include "structmember.h" +#include "pycore_tupleobject.h" /* Support objects whose length is > PY_SSIZE_T_MAX. @@ -71,34 +72,27 @@ make_range_object(PyTypeObject *type, PyObject *start, range(0, 5, -1) */ static PyObject * -range_new(PyTypeObject *type, PyObject *args, PyObject *kw) +range_from_array(PyTypeObject *type, PyObject *const *args, size_t num_args) { rangeobject *obj; PyObject *start = NULL, *stop = NULL, *step = NULL; - if (!_PyArg_NoKeywords("range", kw)) - return NULL; - - Py_ssize_t num_args = PyTuple_GET_SIZE(args); switch (num_args) { case 3: - step = PyTuple_GET_ITEM(args, 2); + step = args[2]; /* fallthrough */ case 2: - start = PyTuple_GET_ITEM(args, 0); - start = PyNumber_Index(start); + /* Convert borrowed refs to owned refs */ + start = PyNumber_Index(args[0]); if (!start) { return NULL; } - - stop = PyTuple_GET_ITEM(args, 1); - stop = PyNumber_Index(stop); + stop = PyNumber_Index(args[1]); if (!stop) { Py_DECREF(start); return NULL; } - - step = validate_step(step); + step = validate_step(step); /* Caution, this can clear exceptions */ if (!step) { Py_DECREF(start); Py_DECREF(stop); @@ -106,8 +100,7 @@ range_new(PyTypeObject *type, PyObject *args, PyObject *kw) } break; case 1: - stop = PyTuple_GET_ITEM(args, 0); - stop = PyNumber_Index(stop); + stop = PyNumber_Index(args[0]); if (!stop) { return NULL; } @@ -122,14 +115,14 @@ range_new(PyTypeObject *type, PyObject *args, PyObject *kw) return NULL; default: PyErr_Format(PyExc_TypeError, - "range expected at most 3 arguments, got %zd", + "range expected at most 3 arguments, got %zu", num_args); return NULL; } - obj = make_range_object(type, start, stop, step); - if (obj != NULL) + if (obj != NULL) { return (PyObject *) obj; + } /* Failed to create object, release attributes */ Py_DECREF(start); @@ -138,64 +131,26 @@ range_new(PyTypeObject *type, PyObject *args, PyObject *kw) return NULL; } +static PyObject * +range_new(PyTypeObject *type, PyObject *args, PyObject *kw) +{ + if (!_PyArg_NoKeywords("range", kw)) + return NULL; + + return range_from_array(type, _PyTuple_ITEMS(args), PyTuple_GET_SIZE(args)); +} + 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; + return range_from_array(type, args, nargs); } PyDoc_STRVAR(range_doc, From 663d8cc6e4eec78225c66116396a1799e93a3caa Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 18 Feb 2020 13:13:47 +0100 Subject: [PATCH 4/5] Use Py_ssize_t for number of arguments --- Objects/rangeobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index 1fe69ddba62931..f4a70ab7db9725 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -72,7 +72,7 @@ make_range_object(PyTypeObject *type, PyObject *start, range(0, 5, -1) */ static PyObject * -range_from_array(PyTypeObject *type, PyObject *const *args, size_t num_args) +range_from_array(PyTypeObject *type, PyObject *const *args, Py_ssize_t num_args) { rangeobject *obj; PyObject *start = NULL, *stop = NULL, *step = NULL; @@ -145,7 +145,7 @@ static PyObject * range_vectorcall(PyTypeObject *type, PyObject *const *args, size_t nargsf, PyObject *kwnames) { - size_t nargs = PyVectorcall_NARGS(nargsf); + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); if (kwnames && PyTuple_GET_SIZE(kwnames) != 0) { PyErr_Format(PyExc_TypeError, "range() takes no keyword arguments"); return NULL; From b4559ce7e3b533c4fb46889ba2f1bd762127b67a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 18 Feb 2020 14:43:05 +0100 Subject: [PATCH 5/5] Reset format argument for Py_ssize_t to %zd --- Objects/rangeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index f4a70ab7db9725..123ca0b032e0e4 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -115,7 +115,7 @@ range_from_array(PyTypeObject *type, PyObject *const *args, Py_ssize_t num_args) return NULL; default: PyErr_Format(PyExc_TypeError, - "range expected at most 3 arguments, got %zu", + "range expected at most 3 arguments, got %zd", num_args); return NULL; }