Skip to content

Commit 266b8c4

Browse files
committed
eager encode, fixed buf
1 parent ee78d43 commit 266b8c4

2 files changed

Lines changed: 34 additions & 55 deletions

File tree

Modules/_remote_debugging/binary_io.h

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,6 @@ static_assert(FILE_FOOTER_SIZE == 32,
109109
/* Maximum stack depth we'll buffer for delta encoding */
110110
#define MAX_STACK_DEPTH 256
111111

112-
/* Initial capacity for RLE pending buffer */
113-
#define INITIAL_RLE_CAPACITY 64
114-
115112
/* Initial capacities for dynamic arrays - sized to reduce reallocations */
116113
#define INITIAL_STRING_CAPACITY 4096
117114
#define INITIAL_FRAME_CAPACITY 4096
@@ -226,12 +223,6 @@ typedef struct {
226223
uint8_t opcode;
227224
} FrameKey;
228225

229-
/* Pending RLE sample - buffered for run-length encoding */
230-
typedef struct {
231-
uint64_t timestamp_delta;
232-
uint8_t status;
233-
} PendingRLESample;
234-
235226
/* Thread entry - tracks per-thread state for delta encoding */
236227
typedef struct {
237228
uint64_t thread_id;
@@ -244,10 +235,9 @@ typedef struct {
244235
size_t prev_stack_capacity;
245236

246237
/* RLE pending buffer - samples waiting to be written as a repeat group */
247-
PendingRLESample *pending_rle;
248-
size_t pending_rle_count;
249-
size_t pending_rle_capacity;
250-
int has_pending_rle; /* Flag: do we have buffered repeats? */
238+
uint8_t *pending_rle;
239+
size_t pending_rle_bytes;
240+
size_t pending_rle_samples;
251241
} ThreadEntry;
252242

253243
/* Main binary writer structure */

Modules/_remote_debugging/binary_io_writer.c

Lines changed: 31 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
/* Frame buffer: depth varint (max 2 bytes for 256) + 256 frames * 5 bytes/varint + margin */
3030
#define MAX_FRAME_BUFFER_SIZE ((MAX_STACK_DEPTH * MAX_VARINT_SIZE_U32) + MAX_VARINT_SIZE_U32 + 16)
3131

32+
/* RLE pending buffer (per thread): one entry is a u64 delta varint + status byte */
33+
#define MAX_RLE_BUF_SIZE (16 * 1024)
34+
#define MAX_RLE_ENTRY_SIZE (MAX_VARINT_SIZE + sizeof(uint8_t))
35+
3236
/* Helper macro: convert PyLong to int32, using default_val if conversion fails */
3337
#define PYLONG_TO_INT32_OR_DEFAULT(obj, var, default_val) \
3438
do { \
@@ -511,15 +515,14 @@ writer_get_or_create_thread_entry(BinaryWriter *writer, uint64_t thread_id,
511515
entry->interpreter_id = interpreter_id;
512516
entry->prev_timestamp = writer->start_time_us;
513517
entry->prev_stack_capacity = MAX_STACK_DEPTH;
514-
entry->pending_rle_capacity = INITIAL_RLE_CAPACITY;
515518

516519
entry->prev_stack = PyMem_Calloc(entry->prev_stack_capacity, sizeof(uint32_t));
517520
if (!entry->prev_stack) {
518521
PyErr_NoMemory();
519522
return NULL;
520523
}
521524

522-
entry->pending_rle = PyMem_Malloc(entry->pending_rle_capacity * sizeof(PendingRLESample));
525+
entry->pending_rle = PyMem_Malloc(MAX_RLE_BUF_SIZE);
523526
if (!entry->pending_rle) {
524527
PyMem_Free(entry->prev_stack);
525528
PyErr_NoMemory();
@@ -626,14 +629,9 @@ write_sample_header(BinaryWriter *writer, ThreadEntry *entry, uint8_t encoding)
626629
static int
627630
flush_pending_rle(BinaryWriter *writer, ThreadEntry *entry)
628631
{
629-
if (!entry->has_pending_rle || entry->pending_rle_count == 0) {
632+
if (entry->pending_rle_samples == 0) {
630633
return 0;
631634
}
632-
if (entry->pending_rle_count > UINT32_MAX - writer->total_samples) {
633-
PyErr_SetString(PyExc_OverflowError,
634-
"too many samples for binary format");
635-
return -1;
636-
}
637635

638636
/* Write RLE record:
639637
* [thread_id: 8] [interpreter_id: 4] [STACK_REPEAT: 1] [count: varint]
@@ -644,31 +642,26 @@ flush_pending_rle(BinaryWriter *writer, ThreadEntry *entry)
644642
return -1;
645643
}
646644

647-
if (writer_write_varint_u32(writer, (uint32_t)entry->pending_rle_count) < 0) {
645+
if (writer_write_varint_u32(writer, (uint32_t)entry->pending_rle_samples) < 0) {
648646
return -1;
649647
}
650648

651-
for (size_t i = 0; i < entry->pending_rle_count; i++) {
652-
if (writer_write_varint_u64(writer, entry->pending_rle[i].timestamp_delta) < 0) {
653-
return -1;
654-
}
655-
if (writer_write_bytes(writer, &entry->pending_rle[i].status, 1) < 0) {
656-
return -1;
657-
}
658-
writer->total_samples++;
649+
if (writer_write_bytes(writer, entry->pending_rle, entry->pending_rle_bytes) < 0) {
650+
return -1;
659651
}
660652

661653
writer->stats.repeat_records++;
662-
writer->stats.repeat_samples += entry->pending_rle_count;
654+
writer->stats.repeat_samples += entry->pending_rle_samples;
663655
/* Each RLE sample saves writing the entire stack */
664-
writer->stats.frames_saved += entry->pending_rle_count * entry->prev_stack_depth;
656+
writer->stats.frames_saved += entry->pending_rle_samples * entry->prev_stack_depth;
665657

666-
entry->pending_rle_count = 0;
667-
entry->has_pending_rle = 0;
658+
entry->pending_rle_bytes = 0;
659+
entry->pending_rle_samples = 0;
668660

669661
return 0;
670662
}
671663

664+
672665
/* Write a single sample with the specified encoding.
673666
* Returns 0 on success, -1 on failure. */
674667
static int
@@ -678,12 +671,6 @@ write_sample_with_encoding(BinaryWriter *writer, ThreadEntry *entry,
678671
const uint32_t *frame_indices, size_t stack_depth,
679672
size_t shared_count, size_t pop_count, size_t push_count)
680673
{
681-
if (writer->total_samples == UINT32_MAX) {
682-
PyErr_SetString(PyExc_OverflowError,
683-
"too many samples for binary format");
684-
return -1;
685-
}
686-
687674
/* Header: thread_id(8) + interpreter_id(4) + encoding(1) + delta(varint) + status(1) */
688675
uint8_t header_buf[SAMPLE_HEADER_MAX_SIZE];
689676
memcpy(header_buf + SMP_OFF_THREAD_ID, &entry->thread_id, SMP_SIZE_THREAD_ID);
@@ -752,7 +739,6 @@ write_sample_with_encoding(BinaryWriter *writer, ThreadEntry *entry,
752739
}
753740

754741
writer->stats.total_frames_written += frames_written;
755-
writer->total_samples++;
756742
return 0;
757743
}
758744

@@ -955,6 +941,12 @@ static int
955941
process_thread_sample(BinaryWriter *writer, PyObject *thread_info,
956942
uint32_t interpreter_id, uint64_t timestamp_us)
957943
{
944+
if (writer->total_samples >= UINT32_MAX) {
945+
PyErr_SetString(PyExc_OverflowError,
946+
"too many samples for binary format");
947+
return -1;
948+
}
949+
958950
PyObject *thread_id_obj = PyStructSequence_GET_ITEM(thread_info, 0);
959951
PyObject *status_obj = PyStructSequence_GET_ITEM(thread_info, 1);
960952
PyObject *frame_list = PyStructSequence_GET_ITEM(thread_info, 2);
@@ -1002,20 +994,18 @@ process_thread_sample(BinaryWriter *writer, PyObject *thread_info,
1002994
* STACK_REPEAT against an empty curr_stack (depth 0). Buffering
1003995
* it here is correct; the RLE flush path emits it as a normal
1004996
* STACK_REPEAT record. */
1005-
if (GROW_ARRAY(entry->pending_rle, entry->pending_rle_count,
1006-
entry->pending_rle_capacity, PendingRLESample) < 0) {
997+
if (entry->pending_rle_bytes + MAX_RLE_ENTRY_SIZE > MAX_RLE_BUF_SIZE
998+
&& flush_pending_rle(writer, entry) < 0) {
1007999
return -1;
10081000
}
1009-
entry->pending_rle[entry->pending_rle_count].timestamp_delta = delta;
1010-
entry->pending_rle[entry->pending_rle_count].status = status;
1011-
entry->pending_rle_count++;
1012-
entry->has_pending_rle = 1;
1001+
entry->pending_rle_bytes += encode_varint_u64(
1002+
entry->pending_rle + entry->pending_rle_bytes, delta);
1003+
entry->pending_rle[entry->pending_rle_bytes++] = status;
1004+
entry->pending_rle_samples++;
10131005
} else {
10141006
/* Stack changed - flush any pending RLE first */
1015-
if (entry->has_pending_rle) {
1016-
if (flush_pending_rle(writer, entry) < 0) {
1017-
return -1;
1018-
}
1007+
if (flush_pending_rle(writer, entry) < 0) {
1008+
return -1;
10191009
}
10201010

10211011
if (write_sample_with_encoding(writer, entry, delta, status, encoding,
@@ -1028,6 +1018,7 @@ process_thread_sample(BinaryWriter *writer, PyObject *thread_info,
10281018
entry->prev_stack_depth = curr_depth;
10291019
}
10301020

1021+
writer->total_samples++;
10311022
return 0;
10321023
}
10331024

@@ -1075,10 +1066,8 @@ int
10751066
binary_writer_finalize(BinaryWriter *writer)
10761067
{
10771068
for (size_t i = 0; i < writer->thread_count; i++) {
1078-
if (writer->thread_entries[i].has_pending_rle) {
1079-
if (flush_pending_rle(writer, &writer->thread_entries[i]) < 0) {
1080-
return -1;
1081-
}
1069+
if (flush_pending_rle(writer, &writer->thread_entries[i]) < 0) {
1070+
return -1;
10821071
}
10831072
}
10841073

0 commit comments

Comments
 (0)