Skip to content

Commit 52144c8

Browse files
committed
perf(profiling): Only sample allocations where the pointer hashes to a given value
1 parent 4d812d8 commit 52144c8

File tree

1 file changed

+57
-15
lines changed

1 file changed

+57
-15
lines changed

ddtrace/profiling/collector/_memalloc_heap.c

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ typedef struct
7373
memalloc_heap_map_t* allocs_m;
7474
/* Bytes allocated since the last sample was collected */
7575
uint64_t allocated_memory;
76+
/* The factor that controls how often pointers are eligible to sample
77+
* TODO more description here */
78+
uintptr_t sample_mask;
7679
/* True if we are exporting the current heap profile */
7780
bool frozen;
7881
/* Contains the ongoing heap allocation/deallocation while frozen */
@@ -87,12 +90,20 @@ typedef struct
8790
/* Debug guard to assert that GIL-protected critical sections are maintained
8891
* while accessing the profiler's state */
8992
memalloc_gil_debug_check_t gil_guard;
93+
// Whether we are currently in a run where we should try to collect a sample
94+
bool collect_sample;
9095
} heap_tracker_t;
9196

9297
static heap_tracker_t global_heap_tracker;
9398

99+
static bool
100+
is_eligible_to_sample_no_cpython(void* ptr)
101+
{
102+
return (uintptr_t)ptr % global_heap_tracker.sample_mask == 0;
103+
}
104+
94105
static uint32_t
95-
heap_tracker_next_sample_size(uint32_t sample_size)
106+
heap_tracker_next_sample_size_no_cpython(uint32_t sample_size)
96107
{
97108
/* We want to draw a sampling target from an exponential distribution with
98109
average sample_size. We use the standard technique of inverse transform
@@ -114,10 +125,12 @@ heap_tracker_init(heap_tracker_t* heap_tracker)
114125
heap_tracker->freezer.allocs_m = memalloc_heap_map_new();
115126
ptr_array_init(&heap_tracker->freezer.frees);
116127
traceback_array_init(&heap_tracker->unreported_samples);
128+
heap_tracker->sample_mask = 1;
117129
heap_tracker->allocated_memory = 0;
118130
heap_tracker->frozen = false;
119131
heap_tracker->sample_size = 0;
120132
heap_tracker->current_sample_size = 0;
133+
heap_tracker->collect_sample = false;
121134
memalloc_gil_debug_check_init(&heap_tracker->gil_guard);
122135
}
123136

@@ -189,8 +202,11 @@ void
189202
memalloc_heap_tracker_init(uint32_t sample_size)
190203
{
191204
heap_tracker_init(&global_heap_tracker);
205+
// TODO take as a param
206+
// Should be prime
207+
global_heap_tracker.sample_mask = 5;
192208
global_heap_tracker.sample_size = sample_size;
193-
global_heap_tracker.current_sample_size = heap_tracker_next_sample_size(sample_size);
209+
global_heap_tracker.current_sample_size = heap_tracker_next_sample_size_no_cpython(sample_size);
194210
}
195211

196212
void
@@ -218,6 +234,13 @@ memalloc_heap_untrack_no_cpython(heap_tracker_t* heap_tracker, void* ptr)
218234
MEMALLOC_GIL_DEBUG_CHECK_RELEASE(&heap_tracker->gil_guard);
219235
return NULL;
220236
}
237+
238+
// If this is not a pointer that could be sampled, then no need to do anything with it.
239+
if (!is_eligible_to_sample_no_cpython(ptr)) {
240+
MEMALLOC_GIL_DEBUG_CHECK_RELEASE(&heap_tracker->gil_guard);
241+
return NULL;
242+
}
243+
221244
if (!heap_tracker->frozen) {
222245
traceback_t* tb = memalloc_heap_map_remove(heap_tracker->allocs_m, ptr);
223246
if (tb && !tb->reported) {
@@ -259,7 +282,7 @@ memalloc_heap_untrack(void* ptr)
259282
* shared state, and must be called with the GIL held and without making any C
260283
* Python API calls. */
261284
static bool
262-
memalloc_heap_should_sample_no_cpython(heap_tracker_t* heap_tracker, size_t size)
285+
memalloc_heap_should_sample_no_cpython(heap_tracker_t* heap_tracker, void* ptr, size_t size)
263286
{
264287
MEMALLOC_GIL_DEBUG_CHECK_ACQUIRE(&heap_tracker->gil_guard);
265288
/* Heap tracking is disabled */
@@ -270,12 +293,6 @@ memalloc_heap_should_sample_no_cpython(heap_tracker_t* heap_tracker, size_t size
270293

271294
heap_tracker->allocated_memory += size;
272295

273-
/* Check if we have enough sample or not */
274-
if (heap_tracker->allocated_memory < heap_tracker->current_sample_size) {
275-
MEMALLOC_GIL_DEBUG_CHECK_RELEASE(&heap_tracker->gil_guard);
276-
return false;
277-
}
278-
279296
if (memalloc_heap_map_size(heap_tracker->allocs_m) + memalloc_heap_map_size(heap_tracker->freezer.allocs_m) >
280297
TRACEBACK_ARRAY_MAX_COUNT) {
281298
/* TODO(nick) this is vestigial from the original array-based
@@ -287,10 +304,39 @@ memalloc_heap_should_sample_no_cpython(heap_tracker_t* heap_tracker, size_t size
287304
return false;
288305
}
289306

307+
/* Check if we have enough sample or not.
308+
* If so, start a run of possible sample collection */
309+
if (heap_tracker->allocated_memory >= heap_tracker->current_sample_size) {
310+
heap_tracker->collect_sample = true;
311+
}
312+
313+
/* If we are in a situation where we could collect samples, and the pointer is elibible,
314+
* collect a sample */
315+
if (heap_tracker->collect_sample && is_eligible_to_sample_no_cpython(ptr)) {
316+
heap_tracker->collect_sample = false;
317+
MEMALLOC_GIL_DEBUG_CHECK_RELEASE(&heap_tracker->gil_guard);
318+
return true;
319+
}
320+
321+
/* Otherwise, leave collect_sample untouched, and try next time */
290322
MEMALLOC_GIL_DEBUG_CHECK_RELEASE(&heap_tracker->gil_guard);
291323
return true;
292324
}
293325

326+
/* Updates the threshold for */
327+
static void
328+
memalloc_heap_update_threshold_no_cpython(heap_tracker_t* heap_tracker)
329+
{
330+
MEMALLOC_GIL_DEBUG_CHECK_ACQUIRE(&heap_tracker->gil_guard);
331+
/* Reset the counter to 0 */
332+
heap_tracker->allocated_memory = 0;
333+
334+
/* Compute the new target sample size */
335+
heap_tracker->current_sample_size = heap_tracker_next_sample_size_no_cpython(heap_tracker->sample_size);
336+
337+
MEMALLOC_GIL_DEBUG_CHECK_RELEASE(&heap_tracker->gil_guard);
338+
}
339+
294340
/* Track an allocation that we decided to sample. This updates shared state and
295341
* must be called with the GIL held and without making any C Python API calls.
296342
* If the allocation could not be added because the profiler was stopped,
@@ -312,11 +358,7 @@ memalloc_heap_add_sample_no_cpython(heap_tracker_t* heap_tracker, traceback_t* t
312358
old = memalloc_heap_map_insert(heap_tracker->allocs_m, tb->ptr, tb);
313359
}
314360

315-
/* Reset the counter to 0 */
316-
heap_tracker->allocated_memory = 0;
317-
318-
/* Compute the new target sample size */
319-
heap_tracker->current_sample_size = heap_tracker_next_sample_size(heap_tracker->sample_size);
361+
memalloc_heap_update_threshold_no_cpython(heap_tracker);
320362

321363
MEMALLOC_GIL_DEBUG_CHECK_RELEASE(&heap_tracker->gil_guard);
322364
return old;
@@ -326,7 +368,7 @@ memalloc_heap_add_sample_no_cpython(heap_tracker_t* heap_tracker, traceback_t* t
326368
void
327369
memalloc_heap_track(uint16_t max_nframe, void* ptr, size_t size, PyMemAllocatorDomain domain)
328370
{
329-
if (!memalloc_heap_should_sample_no_cpython(&global_heap_tracker, size)) {
371+
if (!memalloc_heap_should_sample_no_cpython(&global_heap_tracker, ptr, size)) {
330372
return;
331373
}
332374

0 commit comments

Comments
 (0)