diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 7946550ec0db63..95ffb7e471937c 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -2094,6 +2094,18 @@ def for_iter_tuple(): self.assert_specialized(for_iter_tuple, "FOR_ITER_TUPLE") self.assert_no_opcode(for_iter_tuple, "FOR_ITER") + b = b"\x00\x7f\x80\xff" * 3 + result = [] + def for_iter_bytes(): + result.clear() + for i in b: + result.append(i) + + for_iter_bytes() + self.assertEqual(result, list(b)) + self.assert_specialized(for_iter_bytes, "FOR_ITER_VIRTUAL") + self.assert_no_opcode(for_iter_bytes, "FOR_ITER") + s = "abcdefghij" def for_iter_str(): for i in s: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-12-14-00.gh-issue-132509.bytes-iter.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-12-14-00.gh-issue-132509.bytes-iter.rst new file mode 100644 index 00000000000000..a29cfe9b76f5ff --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-12-14-00.gh-issue-132509.bytes-iter.rst @@ -0,0 +1,2 @@ +Speed up iteration over exact :class:`bytes` objects by inlining item +retrieval in the bytecode interpreter. diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index 7a75e80298fcd8..ab850db2c03649 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -6674,10 +6674,33 @@ iter = stack_pointer[-2]; PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter); Py_ssize_t index = PyStackRef_UntagInt(null_or_index); - _PyFrame_SetStackPointer(frame, stack_pointer); - _PyFrame_StackPointerValidate(frame); - _PyObjectIndexPair next_index = Py_TYPE(iter_o)->_tp_iteritem(iter_o, index); - _PyFrame_StackPointerInvalidate(frame); + _PyObjectIndexPair next_index; + if (PyBytes_CheckExact(iter_o)) { + if ((size_t)index >= (size_t)PyBytes_GET_SIZE(iter_o)) { + next_index = (_PyObjectIndexPair) { + .object = NULL, + .index = index, + }; + } + else { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + unsigned char value = (unsigned char) + ((PyBytesObject *)iter_o)->ob_sval[index]; + _PyFrame_StackPointerInvalidate(frame); + next_index = (_PyObjectIndexPair) { + .object = (PyObject *)&_PyLong_SMALL_INTS[ + _PY_NSMALLNEGINTS + value], + .index = index + 1, + }; + } + } + else { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + next_index = Py_TYPE(iter_o)->_tp_iteritem(iter_o, index); + _PyFrame_StackPointerInvalidate(frame); + } PyObject *next_o = next_index.object; index = next_index.index; if (next_o == NULL) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index fb0cdf4d65e060..0fbbc9c6116b89 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3883,7 +3883,27 @@ dummy_func( replaced op(_FOR_ITER_VIRTUAL, (iter, null_or_index -- iter, null_or_index, next)) { PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter); Py_ssize_t index = PyStackRef_UntagInt(null_or_index); - _PyObjectIndexPair next_index = Py_TYPE(iter_o)->_tp_iteritem(iter_o, index); + _PyObjectIndexPair next_index; + if (PyBytes_CheckExact(iter_o)) { + if ((size_t)index >= (size_t)PyBytes_GET_SIZE(iter_o)) { + next_index = (_PyObjectIndexPair) { + .object = NULL, + .index = index, + }; + } + else { + unsigned char value = (unsigned char) + ((PyBytesObject *)iter_o)->ob_sval[index]; + next_index = (_PyObjectIndexPair) { + .object = (PyObject *)&_PyLong_SMALL_INTS[ + _PY_NSMALLNEGINTS + value], + .index = index + 1, + }; + } + } + else { + next_index = Py_TYPE(iter_o)->_tp_iteritem(iter_o, index); + } PyObject *next_o = next_index.object; index = next_index.index; if (next_o == NULL) { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 77c18b3d61fefc..d5009a550b02b1 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -6674,10 +6674,33 @@ iter = stack_pointer[-2]; PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter); Py_ssize_t index = PyStackRef_UntagInt(null_or_index); - _PyFrame_SetStackPointer(frame, stack_pointer); - _PyFrame_StackPointerValidate(frame); - _PyObjectIndexPair next_index = Py_TYPE(iter_o)->_tp_iteritem(iter_o, index); - _PyFrame_StackPointerInvalidate(frame); + _PyObjectIndexPair next_index; + if (PyBytes_CheckExact(iter_o)) { + if ((size_t)index >= (size_t)PyBytes_GET_SIZE(iter_o)) { + next_index = (_PyObjectIndexPair) { + .object = NULL, + .index = index, + }; + } + else { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + unsigned char value = (unsigned char) + ((PyBytesObject *)iter_o)->ob_sval[index]; + _PyFrame_StackPointerInvalidate(frame); + next_index = (_PyObjectIndexPair) { + .object = (PyObject *)&_PyLong_SMALL_INTS[ + _PY_NSMALLNEGINTS + value], + .index = index + 1, + }; + } + } + else { + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + next_index = Py_TYPE(iter_o)->_tp_iteritem(iter_o, index); + _PyFrame_StackPointerInvalidate(frame); + } PyObject *next_o = next_index.object; index = next_index.index; if (next_o == NULL) {