-
-
Notifications
You must be signed in to change notification settings - Fork 31.5k
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
gh-89189: More compact range iterator #27986
Changes from all commits
2fa0b0d
1cd0138
214f204
3133f66
7a42474
175b0d3
22f76b8
20e3149
abf2971
0ca67c2
10822c0
050df74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,6 @@ extern "C" { | |
|
||
typedef struct { | ||
PyObject_HEAD | ||
long index; | ||
long start; | ||
long step; | ||
long len; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Optimize the :class:`range` object iterator. It is now smaller, faster | ||
iteration of ranges containing large numbers. Smaller pickles, faster | ||
unpickling. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -756,18 +756,19 @@ PyTypeObject PyRange_Type = { | |
static PyObject * | ||
rangeiter_next(_PyRangeIterObject *r) | ||
{ | ||
if (r->index < r->len) | ||
/* cast to unsigned to avoid possible signed overflow | ||
in intermediate calculations. */ | ||
return PyLong_FromLong((long)(r->start + | ||
(unsigned long)(r->index++) * r->step)); | ||
if (r->len > 0) { | ||
long result = r->start; | ||
r->start = result + r->step; | ||
r->len--; | ||
return PyLong_FromLong(result); | ||
} | ||
return NULL; | ||
} | ||
|
||
static PyObject * | ||
rangeiter_len(_PyRangeIterObject *r, PyObject *Py_UNUSED(ignored)) | ||
{ | ||
return PyLong_FromLong(r->len - r->index); | ||
return PyLong_FromLong(r->len); | ||
} | ||
|
||
PyDoc_STRVAR(length_hint_doc, | ||
|
@@ -794,8 +795,8 @@ rangeiter_reduce(_PyRangeIterObject *r, PyObject *Py_UNUSED(ignored)) | |
if (range == NULL) | ||
goto err; | ||
/* return the result */ | ||
return Py_BuildValue( | ||
"N(N)l", _PyEval_GetBuiltin(&_Py_ID(iter)), range, r->index); | ||
return Py_BuildValue("N(N)O", _PyEval_GetBuiltin(&_Py_ID(iter)), | ||
range, Py_None); | ||
err: | ||
Py_XDECREF(start); | ||
Py_XDECREF(stop); | ||
|
@@ -814,7 +815,8 @@ rangeiter_setstate(_PyRangeIterObject *r, PyObject *state) | |
index = 0; | ||
else if (index > r->len) | ||
index = r->len; /* exhausted iterator */ | ||
r->index = index; | ||
r->start += index * r->step; | ||
r->len -= index; | ||
Py_RETURN_NONE; | ||
} | ||
|
||
|
@@ -904,13 +906,11 @@ fast_range_iter(long start, long stop, long step, long len) | |
it->start = start; | ||
it->step = step; | ||
it->len = len; | ||
it->index = 0; | ||
return (PyObject *)it; | ||
} | ||
|
||
typedef struct { | ||
PyObject_HEAD | ||
PyObject *index; | ||
PyObject *start; | ||
PyObject *step; | ||
PyObject *len; | ||
|
@@ -919,7 +919,8 @@ typedef struct { | |
static PyObject * | ||
longrangeiter_len(longrangeiterobject *r, PyObject *no_args) | ||
{ | ||
return PyNumber_Subtract(r->len, r->index); | ||
Py_INCREF(r->len); | ||
return r->len; | ||
} | ||
|
||
static PyObject * | ||
|
@@ -946,8 +947,8 @@ longrangeiter_reduce(longrangeiterobject *r, PyObject *Py_UNUSED(ignored)) | |
} | ||
|
||
/* return the result */ | ||
return Py_BuildValue( | ||
"N(N)O", _PyEval_GetBuiltin(&_Py_ID(iter)), range, r->index); | ||
return Py_BuildValue("N(N)O", _PyEval_GetBuiltin(&_Py_ID(iter)), | ||
range, Py_None); | ||
} | ||
|
||
static PyObject * | ||
|
@@ -970,7 +971,22 @@ longrangeiter_setstate(longrangeiterobject *r, PyObject *state) | |
if (cmp > 0) | ||
state = r->len; | ||
} | ||
Py_XSETREF(r->index, Py_NewRef(state)); | ||
PyObject *product = PyNumber_Multiply(state, r->step); | ||
if (product == NULL) | ||
return NULL; | ||
PyObject *new_start = PyNumber_Add(r->start, product); | ||
Py_DECREF(product); | ||
if (new_start == NULL) | ||
return NULL; | ||
PyObject *new_len = PyNumber_Subtract(r->len, state); | ||
if (new_len == NULL) { | ||
Py_DECREF(new_start); | ||
return NULL; | ||
} | ||
PyObject *tmp = r->start; | ||
r->start = new_start; | ||
Py_SETREF(r->len, new_len); | ||
Py_DECREF(tmp); | ||
Comment on lines
+986
to
+989
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see a scenario where we can't just use
(which would be a little easier to follow). Both I'll leave it up to you though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Py_SETREF can only help when you assign a single object attribute, or if the attributes are independent. Two sequential We do not check the type of It never occurs in normal code ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aha, that's a scenario I hadn't considered. Maybe the next time I am asked in some kind of Q&A or interview "do you have any regrets" I should mention |
||
Py_RETURN_NONE; | ||
} | ||
|
||
|
@@ -987,7 +1003,6 @@ static PyMethodDef longrangeiter_methods[] = { | |
static void | ||
longrangeiter_dealloc(longrangeiterobject *r) | ||
{ | ||
Py_XDECREF(r->index); | ||
Py_XDECREF(r->start); | ||
Py_XDECREF(r->step); | ||
Py_XDECREF(r->len); | ||
|
@@ -997,29 +1012,21 @@ longrangeiter_dealloc(longrangeiterobject *r) | |
static PyObject * | ||
longrangeiter_next(longrangeiterobject *r) | ||
{ | ||
PyObject *product, *new_index, *result; | ||
if (PyObject_RichCompareBool(r->index, r->len, Py_LT) != 1) | ||
if (PyObject_RichCompareBool(r->len, _PyLong_GetZero(), Py_GT) != 1) | ||
return NULL; | ||
|
||
new_index = PyNumber_Add(r->index, _PyLong_GetOne()); | ||
if (!new_index) | ||
PyObject *new_start = PyNumber_Add(r->start, r->step); | ||
if (new_start == NULL) { | ||
return NULL; | ||
|
||
product = PyNumber_Multiply(r->index, r->step); | ||
if (!product) { | ||
Py_DECREF(new_index); | ||
return NULL; | ||
} | ||
|
||
result = PyNumber_Add(r->start, product); | ||
Py_DECREF(product); | ||
if (result) { | ||
Py_SETREF(r->index, new_index); | ||
} | ||
else { | ||
Py_DECREF(new_index); | ||
PyObject *new_len = PyNumber_Subtract(r->len, _PyLong_GetOne()); | ||
if (new_len == NULL) { | ||
Py_DECREF(new_start); | ||
return NULL; | ||
} | ||
|
||
PyObject *result = r->start; | ||
r->start = new_start; | ||
Py_SETREF(r->len, new_len); | ||
return result; | ||
} | ||
|
||
|
@@ -1108,7 +1115,6 @@ range_iter(PyObject *seq) | |
it->start = Py_NewRef(r->start); | ||
it->step = Py_NewRef(r->step); | ||
it->len = Py_NewRef(r->length); | ||
it->index = Py_NewRef(_PyLong_GetZero()); | ||
return (PyObject *)it; | ||
} | ||
|
||
|
@@ -1186,7 +1192,7 @@ range_reverse(PyObject *seq, PyObject *Py_UNUSED(ignored)) | |
it = PyObject_New(longrangeiterobject, &PyLongRangeIter_Type); | ||
if (it == NULL) | ||
return NULL; | ||
it->index = it->start = it->step = NULL; | ||
it->start = it->step = NULL; | ||
|
||
/* start + (len - 1) * step */ | ||
it->len = Py_NewRef(range->length); | ||
|
@@ -1210,7 +1216,6 @@ range_reverse(PyObject *seq, PyObject *Py_UNUSED(ignored)) | |
if (!it->step) | ||
goto create_failure; | ||
|
||
it->index = Py_NewRef(_PyLong_GetZero()); | ||
return (PyObject *)it; | ||
|
||
create_failure: | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we care, but we might -- am I right that this writes pickles that can't be read by 3.11 or before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, absolutely no. I would never propose such change.
Internals will be different, but the unpickled iterator wil produce the same values.