Skip to content

Commit 1433cd3

Browse files
committed
Add specialization for range iterators and generators, both about as
thread-safe as without spcialization (i.e. not much to none at all).
1 parent 54c551e commit 1433cd3

File tree

5 files changed

+41
-26
lines changed

5 files changed

+41
-26
lines changed

Objects/rangeobject.c

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
88
#include "pycore_range.h"
99
#include "pycore_tuple.h" // _PyTuple_ITEMS()
10+
#include "pycore_pyatomic_ft_wrappers.h"
1011

1112

1213
/* Support objects whose length is > PY_SSIZE_T_MAX.
@@ -816,10 +817,12 @@ PyTypeObject PyRange_Type = {
816817
static PyObject *
817818
rangeiter_next(_PyRangeIterObject *r)
818819
{
819-
if (r->len > 0) {
820-
long result = r->start;
821-
r->start = result + r->step;
822-
r->len--;
820+
long len = FT_ATOMIC_LOAD_LONG_RELAXED(r->len);
821+
if (len > 0) {
822+
long result = FT_ATOMIC_LOAD_LONG_RELAXED(r->start);
823+
FT_ATOMIC_STORE_LONG_RELAXED(r->start, result + r->step);
824+
// Relaxed ops for maximum speed and minimum thread-safety.
825+
FT_ATOMIC_STORE_LONG_RELAXED(r->len, len - 1);
823826
return PyLong_FromLong(result);
824827
}
825828
return NULL;
@@ -828,7 +831,7 @@ rangeiter_next(_PyRangeIterObject *r)
828831
static PyObject *
829832
rangeiter_len(_PyRangeIterObject *r, PyObject *Py_UNUSED(ignored))
830833
{
831-
return PyLong_FromLong(r->len);
834+
return PyLong_FromLong(FT_ATOMIC_LOAD_LONG_RELAXED(r->len));
832835
}
833836

834837
PyDoc_STRVAR(length_hint_doc,
@@ -841,10 +844,11 @@ rangeiter_reduce(_PyRangeIterObject *r, PyObject *Py_UNUSED(ignored))
841844
PyObject *range;
842845

843846
/* create a range object for pickling */
844-
start = PyLong_FromLong(r->start);
847+
long lstart = FT_ATOMIC_LOAD_LONG_RELAXED(r->start);
848+
start = PyLong_FromLong(lstart);
845849
if (start == NULL)
846850
goto err;
847-
stop = PyLong_FromLong(r->start + r->len * r->step);
851+
stop = PyLong_FromLong(lstart + FT_ATOMIC_LOAD_LONG_RELAXED(r->len) * r->step);
848852
if (stop == NULL)
849853
goto err;
850854
step = PyLong_FromLong(r->step);
@@ -871,12 +875,14 @@ rangeiter_setstate(_PyRangeIterObject *r, PyObject *state)
871875
if (index == -1 && PyErr_Occurred())
872876
return NULL;
873877
/* silently clip the index value */
878+
long len = FT_ATOMIC_LOAD_LONG_RELAXED(r->len);
874879
if (index < 0)
875880
index = 0;
876-
else if (index > r->len)
877-
index = r->len; /* exhausted iterator */
878-
r->start += index * r->step;
879-
r->len -= index;
881+
else if (index > len)
882+
index = len; /* exhausted iterator */
883+
FT_ATOMIC_STORE_LONG_RELAXED(r->start,
884+
FT_ATOMIC_LOAD_LONG_RELAXED(r->start) + index * r->step);
885+
FT_ATOMIC_STORE_LONG_RELAXED(r->len, len - index);
880886
Py_RETURN_NONE;
881887
}
882888

Python/bytecodes.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3179,7 +3179,7 @@ dummy_func(
31793179
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
31803180
assert(Py_TYPE(r) == &PyRangeIter_Type);
31813181
STAT_INC(FOR_ITER, hit);
3182-
if (r->len <= 0) {
3182+
if (FT_ATOMIC_LOAD_LONG_RELAXED(r->len) <= 0) {
31833183
// Jump over END_FOR instruction.
31843184
JUMPBY(oparg + 1);
31853185
DISPATCH();
@@ -3190,16 +3190,19 @@ dummy_func(
31903190
op(_GUARD_NOT_EXHAUSTED_RANGE, (iter -- iter)) {
31913191
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
31923192
assert(Py_TYPE(r) == &PyRangeIter_Type);
3193-
EXIT_IF(r->len <= 0);
3193+
EXIT_IF(FT_ATOMIC_LOAD_LONG_RELAXED(r->len) <= 0);
31943194
}
31953195

31963196
op(_ITER_NEXT_RANGE, (iter -- iter, next)) {
31973197
_PyRangeIterObject *r = (_PyRangeIterObject *)PyStackRef_AsPyObjectBorrow(iter);
31983198
assert(Py_TYPE(r) == &PyRangeIter_Type);
3199+
#ifndef Py_GIL_DISABLED
31993200
assert(r->len > 0);
3200-
long value = r->start;
3201-
r->start = value + r->step;
3202-
r->len--;
3201+
#endif
3202+
long value = FT_ATOMIC_LOAD_LONG_RELAXED(r->start);
3203+
FT_ATOMIC_STORE_LONG_RELAXED(r->start, value + r->step);
3204+
FT_ATOMIC_STORE_LONG_RELAXED(r->len,
3205+
FT_ATOMIC_LOAD_LONG_RELAXED(r->len) - 1);
32033206
PyObject *res = PyLong_FromLong(value);
32043207
ERROR_IF(res == NULL, error);
32053208
next = PyStackRef_FromPyObjectSteal(res);

Python/executor_cases.c.h

Lines changed: 7 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

Lines changed: 7 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/specialize.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2668,12 +2668,13 @@ _Py_Specialize_ForIter(_PyStackRef iter, _Py_CODEUNIT *instr, int oparg)
26682668
specialize(instr, FOR_ITER_TUPLE);
26692669
return;
26702670
}
2671-
#ifndef Py_GIL_DISABLED
26722671
else if (tp == &PyRangeIter_Type) {
26732672
specialize(instr, FOR_ITER_RANGE);
26742673
return;
26752674
}
26762675
else if (tp == &PyGen_Type && oparg <= SHRT_MAX) {
2676+
// Generators are very much not thread-safe, so don't worry about
2677+
// the specialization not being thread-safe.
26772678
assert(instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == END_FOR ||
26782679
instr[oparg + INLINE_CACHE_ENTRIES_FOR_ITER + 1].op.code == INSTRUMENTED_END_FOR
26792680
);
@@ -2686,7 +2687,6 @@ _Py_Specialize_ForIter(_PyStackRef iter, _Py_CODEUNIT *instr, int oparg)
26862687
specialize(instr, FOR_ITER_GEN);
26872688
return;
26882689
}
2689-
#endif
26902690
SPECIALIZATION_FAIL(FOR_ITER,
26912691
_PySpecialization_ClassifyIterator(iter_o));
26922692
unspecialize(instr);

0 commit comments

Comments
 (0)