11#include " _memalloc_heap_map.hpp"
22#include " _memalloc_debug.h"
33
4- /* Note that the HeapSample tables will, in general, never free their backing
5- * memory unless we completely clear them. The table takes 17 bytes per entry: 8
6- * for the void* keys, 8 for the traceback* values, and 1 byte per entry for
7- * control metadata. Assuming a load factor target of ~50%, meaning our table
8- * has roughly twice as many slots as actual entries, then for our default
9- * maximum of 2^16 entries the table will be about 2MiB. A table this large
10- * would correspond to a program with a ~65GiB live heap with a 1MiB default
11- * sampling interval. Most of the memory usage of the profiler will come from
12- * the tracebacks themselves, which we _do_ free when we're done with them.
4+ /* Note that the heap tracking tables will, in general, never free their backing
5+ * memory unless we completely clear them. Abseil's flat_hash_map uses approximately
6+ * 8 bytes for the key (void*), 8 bytes for the value (traceback_t*), plus metadata.
7+ * With a load factor of ~87.5% (Abseil's default), the table is quite efficient.
8+ * For our default maximum of 2^16 entries, the table will be roughly 2-3 MiB.
9+ * A table this large would correspond to a program with a ~65GiB live heap with
10+ * a 1MiB default sampling interval. Most of the memory usage of the profiler will
11+ * come from the tracebacks themselves, which we _do_ free when we're done with them.
1312 */
1413
1514// memalloc_heap_map implementation
16- memalloc_heap_map::memalloc_heap_map ()
17- : map(HeapSamples_new(0 ))
18- {
19- }
20-
2115memalloc_heap_map::~memalloc_heap_map ()
2216{
23- HeapSamples_CIter it = HeapSamples_citer (& map);
24- for (const HeapSamples_Entry* e = HeapSamples_CIter_get (&it); e != nullptr ; e = HeapSamples_CIter_next (&it) ) {
25- delete e-> val ;
17+ // Delete all traceback objects before map is destroyed
18+ for (auto & [key, tb] : map ) {
19+ delete tb ;
2620 }
27- HeapSamples_destroy (&map);
28- }
29-
30- size_t
31- memalloc_heap_map::size () const
32- {
33- return HeapSamples_size (&map);
3421}
3522
3623traceback_t *
3724memalloc_heap_map::insert (void * key, traceback_t * value)
3825{
39- HeapSamples_Entry k = { .key = key, .val = value };
40- HeapSamples_Insert res = HeapSamples_insert (&map, &k);
41- traceback_t * prev = nullptr ;
42- if (!res.inserted ) {
26+ // Try to insert the new value
27+ auto [it, inserted] = map.insert ({ key, value });
28+
29+ if (!inserted) {
30+ // Key already existed, replace the value
4331 /* This should not happen. It means we did not properly remove a previously-tracked
4432 * allocation from the map. This should probably be an assertion. Return the previous
4533 * entry as it is for an allocation that has been freed. */
46- HeapSamples_Entry* e = HeapSamples_Iter_get (&res. iter ) ;
47- prev = e-> val ;
48- e-> val = value ;
34+ traceback_t * prev = it-> second ;
35+ it-> second = value ;
36+ return prev ;
4937 }
50- return prev;
51- }
5238
53- bool
54- memalloc_heap_map::contains (void * key) const
55- {
56- return HeapSamples_contains (&map, &key);
39+ return nullptr ;
5740}
5841
5942traceback_t *
6043memalloc_heap_map::remove (void * key)
6144{
62- traceback_t * res = nullptr ;
63- HeapSamples_Iter it = HeapSamples_find (&map, &key);
64- HeapSamples_Entry* e = HeapSamples_Iter_get (&it);
65- if (e != nullptr ) {
66- res = e->val ;
67- /* This erases the entry but won't shrink the table. */
68- HeapSamples_erase_at (it);
45+ auto it = map.find (key);
46+ if (it == map.end ()) {
47+ return nullptr ;
6948 }
70- return res;
49+
50+ traceback_t * result = it->second ;
51+ map.erase (it);
52+ return result;
7153}
7254
7355PyObject*
7456memalloc_heap_map::export_to_python () const
7557{
76- PyObject* heap_list = PyList_New (HeapSamples_size (& map));
58+ PyObject* heap_list = PyList_New (map. size ( ));
7759 if (heap_list == nullptr ) {
7860 return nullptr ;
7961 }
8062
81- int i = 0 ;
82- HeapSamples_CIter it = HeapSamples_citer (&map);
83- for (const HeapSamples_Entry* e = HeapSamples_CIter_get (&it); e != nullptr ; e = HeapSamples_CIter_next (&it)) {
84- traceback_t * tb = e->val ;
85-
63+ size_t i = 0 ;
64+ for (const auto & [key, tb] : map) {
8665 PyObject* tb_and_size = PyTuple_New (2 );
8766 PyTuple_SET_ITEM (tb_and_size, 0 , tb->to_tuple ());
8867 PyTuple_SET_ITEM (tb_and_size, 1 , PyLong_FromSize_t (tb->size ));
89- PyList_SET_ITEM (heap_list, i, tb_and_size);
90- i++;
68+ PyList_SET_ITEM (heap_list, i++, tb_and_size);
9169
9270 memalloc_debug_gil_release ();
9371 }
@@ -97,79 +75,10 @@ memalloc_heap_map::export_to_python() const
9775void
9876memalloc_heap_map::destructive_copy_from (memalloc_heap_map& src)
9977{
100- HeapSamples_Iter it = HeapSamples_iter (&src.map );
101- for (const HeapSamples_Entry* e = HeapSamples_Iter_get (&it); e != nullptr ; e = HeapSamples_Iter_next (&it)) {
102- HeapSamples_insert (&map, e);
103- }
104- /* Can't erase inside the loop or the iterator is invalidated */
105- HeapSamples_clear (&src.map );
106- }
107-
108- // Iterator implementation
109- memalloc_heap_map::iterator::iterator ()
110- : iter{}
111- {
112- }
78+ // Move all entries from src to this map using merge (C++17)
79+ // This efficiently transfers ownership without copying
80+ map.merge (src.map );
11381
114- memalloc_heap_map::iterator::iterator (const memalloc_heap_map& map)
115- : iter(HeapSamples_citer(&map.map))
116- {
117- }
118-
119- memalloc_heap_map::iterator&
120- memalloc_heap_map::iterator::operator ++()
121- {
122- const HeapSamples_Entry* e = HeapSamples_CIter_get (&iter);
123- if (!e) {
124- return *this ;
125- }
126- HeapSamples_CIter_next (&iter);
127- return *this ;
128- }
129-
130- memalloc_heap_map::iterator
131- memalloc_heap_map::iterator::operator ++(int )
132- {
133- iterator tmp = *this ;
134- ++(*this );
135- return tmp;
136- }
137-
138- memalloc_heap_map::iterator::value_type
139- memalloc_heap_map::iterator::operator *() const
140- {
141- const HeapSamples_Entry* e = HeapSamples_CIter_get (&iter);
142- if (!e) {
143- return { nullptr , nullptr };
144- }
145- return { e->key , e->val };
146- }
147-
148- bool
149- memalloc_heap_map::iterator::operator ==(const iterator& other) const
150- {
151- // Compare underlying iterators by their current entry pointers
152- // Note: HeapSamples_CIter doesn't have equality comparison, so we compare
153- // the current entry pointers. Both end iterators will have nullptr entries.
154- const HeapSamples_Entry* e1 = HeapSamples_CIter_get (&iter);
155- const HeapSamples_Entry* e2 = HeapSamples_CIter_get (&other.iter );
156- return e1 == e2 ;
157- }
158-
159- bool
160- memalloc_heap_map::iterator::operator !=(const iterator& other) const
161- {
162- return !(*this == other);
163- }
164-
165- memalloc_heap_map::iterator
166- memalloc_heap_map::begin () const
167- {
168- return iterator (*this );
169- }
170-
171- memalloc_heap_map::iterator
172- memalloc_heap_map::end () const
173- {
174- return iterator ();
82+ // Clear any remaining entries in src (shouldn't be any after merge)
83+ src.map .clear ();
17584}
0 commit comments