Skip to content

Commit 1dac106

Browse files
committed
gh-152721: Fix quadratic RLE replay time in the profiling binary reader
1 parent ecdef17 commit 1dac106

3 files changed

Lines changed: 54 additions & 2 deletions

File tree

Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,49 @@ def test_alternating_threads_status_changes(self):
11751175
self.assertEqual(count, 100)
11761176
self.assert_samples_equal(samples, collector)
11771177

1178+
def test_rle_alternating_status_batches_correctly(self):
1179+
"""A repeat record whose status alternates every sample replays as N
1180+
single-status batches with the right cumulative timestamps."""
1181+
class BatchCollector:
1182+
def __init__(self):
1183+
self.batches = []
1184+
1185+
def collect(self, stack_frames, timestamps_us):
1186+
for interp in stack_frames:
1187+
for thread in interp.threads:
1188+
self.batches.append(
1189+
(thread.status, list(timestamps_us))
1190+
)
1191+
1192+
def export(self, filename):
1193+
pass
1194+
1195+
num_samples = 2000
1196+
frame = make_frame("rle.py", 42, "rle_func")
1197+
with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
1198+
filename = f.name
1199+
self.temp_files.append(filename)
1200+
1201+
writer = BinaryCollector(filename, 1000, compression="none")
1202+
expected = []
1203+
for i in range(num_samples):
1204+
status = THREAD_STATUS_HAS_GIL if i % 2 else 0
1205+
ts = 1000 + i
1206+
expected.append((status, [ts]))
1207+
sample = [
1208+
make_interpreter(0, [make_thread(1, [frame], status)])
1209+
]
1210+
writer.collect(sample, timestamp_us=ts)
1211+
writer.export(None)
1212+
1213+
collector = BatchCollector()
1214+
with BinaryReader(filename) as reader:
1215+
count = reader.replay_samples(collector)
1216+
1217+
self.assertEqual(count, num_samples)
1218+
self.assertEqual(len(collector.batches), num_samples)
1219+
self.assertEqual(collector.batches, expected)
1220+
11781221

11791222
class TestBinaryStress(BinaryFormatTestBase):
11801223
"""Randomized stress tests for binary format."""
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix quadratic replay time in the :mod:`profiling.sampling` binary reader when a
2+
profile's run-length-encoded samples alternate thread status.

Modules/_remote_debugging/binary_io_reader.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,8 @@ binary_reader_replay(BinaryReader *reader, PyObject *collector, PyObject *progre
11051105
return -1;
11061106
}
11071107
}
1108-
timestamps_list = PyList_New(count - i);
1108+
/* Exact-size the list; alloc+trim is O(count^2). */
1109+
timestamps_list = PyList_New(0);
11091110
if (!timestamps_list) {
11101111
return -1;
11111112
}
@@ -1118,7 +1119,13 @@ binary_reader_replay(BinaryReader *reader, PyObject *collector, PyObject *progre
11181119
Py_DECREF(timestamps_list);
11191120
return -1;
11201121
}
1121-
PyList_SET_ITEM(timestamps_list, batch_idx++, ts_obj);
1122+
int append_rc = PyList_Append(timestamps_list, ts_obj);
1123+
Py_DECREF(ts_obj);
1124+
if (append_rc < 0) {
1125+
Py_DECREF(timestamps_list);
1126+
return -1;
1127+
}
1128+
batch_idx++;
11221129
}
11231130

11241131
/* Emit final batch */

0 commit comments

Comments
 (0)