Skip to content

Commit b861e67

Browse files
committed
gh-148323: release the GIL in bytes.join when operands are immutable
1 parent 639f218 commit b861e67

3 files changed

Lines changed: 18 additions & 7 deletions

File tree

Lib/test/test_bytes.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -629,14 +629,20 @@ def test_join(self):
629629
self.assertEqual(dot_join([b"ab", memoryview(b"cd")]), b"ab.:cd")
630630
self.assertEqual(dot_join([bytearray(b"ab"), b"cd"]), b"ab.:cd")
631631
self.assertEqual(dot_join([b"ab", bytearray(b"cd")]), b"ab.:cd")
632-
# Stress it with many items
633-
seq = [b"abc"] * 100000
634-
expected = b"abc" + b".:abc" * 99999
632+
# Stress it with many items or many views on immutable bytes
633+
N = 0x100000 # threshold for releasing the GIL in join()
634+
seq = [b"abc"] * N
635+
expected = b"abc" + b".:abc" * (N - 1)
636+
self.assertGreater(len(expected), N)
635637
self.assertEqual(dot_join(seq), expected)
638+
views = list(map(memoryview, seq))
639+
self.assertEqual(dot_join(views), expected)
636640
# Stress test with empty separator
637-
seq = [b"abc"] * 100000
638-
expected = b"abc" * 100000
641+
expected = b"abc" * N
642+
self.assertGreater(len(expected), N)
639643
self.assertEqual(self.type2test(b"").join(seq), expected)
644+
views = list(map(memoryview, seq))
645+
self.assertEqual(self.type2test(b"").join(views), expected)
640646
self.assertRaises(TypeError, self.type2test(b" ").join, None)
641647
# Error handling and cleanup when some item in the middle of the
642648
# sequence has the wrong type.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Improve performance of :meth:`bytes.join` when the operands are known to be
2+
buffer views on :class:`bytes` objects (but not subclasses thereof) by
3+
releasing the GIL during the concatenation. Patch by Bénédikt Tran.

Objects/stringlib/join.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ STRINGLIB(bytes_join)(PyObject *sep, PyObject *iterable)
1818
Py_buffer *buffers = NULL;
1919
#define NB_STATIC_BUFFERS 10
2020
Py_buffer static_buffers[NB_STATIC_BUFFERS];
21-
#define GIL_THRESHOLD 1048576
21+
#define GIL_THRESHOLD 1048576 // 0x100000
2222
int drop_gil = 1;
2323
PyThreadState *save = NULL;
2424

@@ -81,7 +81,9 @@ STRINGLIB(bytes_join)(PyObject *sep, PyObject *iterable)
8181
* races anyway, but this is a conservative approach that avoids
8282
* changing the behaviour of that data race.
8383
*/
84-
drop_gil = 0;
84+
if (!PyBytes_CheckExact(buffers[i].obj)) {
85+
drop_gil = 0;
86+
}
8587
}
8688
nbufs = i + 1; /* for error cleanup */
8789
itemlen = buffers[i].len;

0 commit comments

Comments
 (0)