Skip to content

Commit 8dba039

Browse files
authored
gh-158585: Optimize bytes object creation (#158664)
Add bytes_alloc() helper function. bytes_alloc() and _PyBytes_FromStringAndSize() have less conditional branches than previous code, and so should be a little bit faster. * Rename existing bytes_alloc() to bytes_type_alloc(). * Replace _PyBytes_FromSize(calloc=1) with _PyBytes_FromSizeZero(). * Add set_ob_shash_unsafe(): similar to set_ob_shash() but don't use an atomic operation on Free Threading. Use this new function on newly allocated bytes objects and in bytes_resize_inplace(). * Replace PyBytes_FromStringAndSize(NULL, size) with bytes_alloc(size). * Use bytes_alloc() in PyBytes_FromString() and _PyBytes_Repeat(). * Add _PyBytes_FromStringAndSize(): similar to PyBytes_FromStringAndSize(), but str must not be NULL. * Replace PyBytes_FromStringAndSize() with _PyBytes_FromStringAndSize(). * Test that PyBytes_FromString() and PyBytes_FromStringAndSize() return singletons for 0 or 1 bytes. * Fix test_tracemalloc.test_set_traceback_limit().
1 parent 9d22a53 commit 8dba039

4 files changed

Lines changed: 183 additions & 92 deletions

File tree

‎Lib/test/test_bytes.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ def test_concat(self):
410410
self.assertRaises(TypeError, lambda: b1 + "def")
411411
self.assertRaises(TypeError, lambda: "abc" + b2)
412412

413+
self.assertEqual(self.type2test() + memoryview(b''), b'')
414+
413415
def test_repeat(self):
414416
for b in b"abc", self.type2test(b"abc"):
415417
self.assertEqual(b * 3, b"abcabcabc")
@@ -1447,6 +1449,45 @@ class SubBytes(bytes):
14471449
self.assertNotEqual(id(s), id(1 * s))
14481450
self.assertNotEqual(id(s), id(s * 2))
14491451

1452+
@support.cpython_only
1453+
def test_concat_cpython(self):
1454+
# Test optimizations
1455+
empty = b''
1456+
abc = b"abc"
1457+
self.assertIs(abc + empty, abc)
1458+
self.assertIs(empty + abc, abc)
1459+
self.assertIs(empty + bytearray(b''), empty)
1460+
self.assertIs(empty + memoryview(b''), empty)
1461+
1462+
class Subclass(bytes):
1463+
pass
1464+
1465+
empty_subclass = Subclass(b"")
1466+
self.assertIs(empty + empty_subclass, empty)
1467+
self.assertIs(empty_subclass + empty, empty)
1468+
self.assertIs(abc + empty_subclass, abc)
1469+
self.assertIs(empty_subclass + abc, abc)
1470+
1471+
def assert_copy(result, expected):
1472+
self.assertIsNot(result, expected)
1473+
self.assertEqual(type(result), bytes)
1474+
self.assertEqual(result, expected)
1475+
1476+
# Copy the string if it's a subclass
1477+
def_subclass = Subclass(b"def")
1478+
assert_copy(def_subclass + empty, b'def')
1479+
assert_copy(empty + def_subclass, b'def')
1480+
assert_copy(abc + def_subclass, b'abcdef')
1481+
assert_copy(def_subclass + abc, b'defabc')
1482+
1483+
@support.cpython_only
1484+
def test_repeat_cpython(self):
1485+
# Test optimizations
1486+
for b in b'', b'x', b"abc":
1487+
self.assertIs(b * 1, b)
1488+
self.assertIs(b * 0, b'')
1489+
self.assertIs(b * -23, b'')
1490+
14501491

14511492
class ByteArrayTest(BaseBytesTest, unittest.TestCase):
14521493
type2test = bytearray

‎Lib/test/test_capi/test_bytes.py‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@ def test_fromstringandsize(self):
6161
self.assertEqual(fromstringandsize(b'abc'), b'abc')
6262
self.assertEqual(fromstringandsize(b'abc', 2), b'ab')
6363
self.assertEqual(fromstringandsize(b'abc\0def'), b'abc\0def')
64-
self.assertEqual(fromstringandsize(b'a'), b'a')
65-
self.assertEqual(fromstringandsize(b'a', 1), b'a')
66-
self.assertEqual(fromstringandsize(b'', 0), b'')
67-
self.assertEqual(fromstringandsize(NULL, 0), b'')
64+
65+
# Return singleton for 0 or 1 bytes
66+
self.assertIs(fromstringandsize(b'', 0), b'')
67+
self.assertIs(fromstringandsize(NULL, 0), b'')
68+
self.assertIs(fromstringandsize(b'a', 1), b'a')
69+
6870
self.assertEqual(len(fromstringandsize(NULL, 3)), 3)
6971
self.assertRaises((MemoryError, OverflowError),
7072
fromstringandsize, NULL, PY_SSIZE_T_MAX)
@@ -79,7 +81,10 @@ def test_fromstring(self):
7981
fromstring = _testlimitedcapi.bytes_fromstring
8082

8183
self.assertEqual(fromstring(b'abc\0def'), b'abc')
82-
self.assertEqual(fromstring(b''), b'')
84+
85+
# Return singleton for 0 or 1 bytes
86+
self.assertIs(fromstring(b''), b'')
87+
self.assertIs(fromstring(b'a'), b'a')
8388

8489
# CRASHES fromstring(NULL)
8590

‎Lib/test/test_tracemalloc.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ def get_frames(nframe, lineno_delta):
4141
def allocate_bytes(size):
4242
nframe = tracemalloc.get_traceback_limit()
4343
bytes_len = (size - EMPTY_STRING_SIZE)
44+
if bytes_len < 0:
45+
raise ValueError(f"{size} bytes is too small")
4446
frames = get_frames(nframe, 1)
4547
data = b'x' * bytes_len
4648
return data, tracemalloc.Traceback(frames, min(len(frames), nframe))
@@ -158,7 +160,7 @@ def test_new_reference(self):
158160
self.assertEqual(traceback, obj_traceback)
159161

160162
def test_set_traceback_limit(self):
161-
obj_size = 10
163+
obj_size = 123
162164

163165
tracemalloc.stop()
164166
self.assertRaises(ValueError, tracemalloc.start, -1)

0 commit comments

Comments
 (0)