diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index b1cdbe04765ed0..d07400b3d4f835 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -629,14 +629,20 @@ def test_join(self): self.assertEqual(dot_join([b"ab", memoryview(b"cd")]), b"ab.:cd") self.assertEqual(dot_join([bytearray(b"ab"), b"cd"]), b"ab.:cd") self.assertEqual(dot_join([b"ab", bytearray(b"cd")]), b"ab.:cd") - # Stress it with many items - seq = [b"abc"] * 100000 - expected = b"abc" + b".:abc" * 99999 + # Stress it with many items or many views on immutable bytes + N = 0x100000 # threshold for releasing the GIL in join() + seq = [b"abc"] * N + expected = b"abc" + b".:abc" * (N - 1) + self.assertGreater(len(expected), N) self.assertEqual(dot_join(seq), expected) + views = list(map(memoryview, seq)) + self.assertEqual(dot_join(views), expected) # Stress test with empty separator - seq = [b"abc"] * 100000 - expected = b"abc" * 100000 + expected = b"abc" * N + self.assertGreater(len(expected), N) self.assertEqual(self.type2test(b"").join(seq), expected) + views = list(map(memoryview, seq)) + self.assertEqual(self.type2test(b"").join(views), expected) self.assertRaises(TypeError, self.type2test(b" ").join, None) # Error handling and cleanup when some item in the middle of the # sequence has the wrong type. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-12-17-36-12.gh-issue-148323.HicoJQ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-12-17-36-12.gh-issue-148323.HicoJQ.rst new file mode 100644 index 00000000000000..b94d8e0a53c1a9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-12-17-36-12.gh-issue-148323.HicoJQ.rst @@ -0,0 +1,3 @@ +Improve performance of :meth:`bytes.join` when the operands are known to be +buffer views on :class:`bytes` objects (but not subclasses thereof) by +releasing the GIL during the concatenation. Patch by Bénédikt Tran. diff --git a/Objects/stringlib/join.h b/Objects/stringlib/join.h index de6bd83ffe4c8b..470daf980f50b6 100644 --- a/Objects/stringlib/join.h +++ b/Objects/stringlib/join.h @@ -81,7 +81,10 @@ STRINGLIB(bytes_join)(PyObject *sep, PyObject *iterable) * races anyway, but this is a conservative approach that avoids * changing the behaviour of that data race. */ - drop_gil = 0; + PyObject *bufobj = buffers[i].obj; + if (!bufobj || !PyBytes_CheckExact(bufobj)) { + drop_gil = 0; + } } nbufs = i + 1; /* for error cleanup */ itemlen = buffers[i].len;