From b699033ab31c9bbc4a3304dbd64dc03112497316 Mon Sep 17 00:00:00 2001 From: Chip Weinberger Date: Fri, 17 Feb 2023 14:22:17 -0800 Subject: [PATCH 1/3] [Heap Trace] Perf: use hash map to speed up leaks mode --- components/heap/heap_trace_standalone.c | 254 +++++++++++++++++------ components/heap/include/esp_heap_trace.h | 23 ++ 2 files changed, 209 insertions(+), 68 deletions(-) diff --git a/components/heap/heap_trace_standalone.c b/components/heap/heap_trace_standalone.c index c422fa84325..4ce549308d7 100644 --- a/components/heap/heap_trace_standalone.c +++ b/components/heap/heap_trace_standalone.c @@ -56,24 +56,43 @@ typedef struct { bool has_overflowed; } records_t; +typedef struct { + + /* Buffer used for hashmap entries */ + heap_trace_hashmap_entry_t *buffer; + + /* length of 'buffer' */ + size_t count; +} hashmap_t; + // Forward Defines static void heap_trace_dump_base(bool internal_ram, bool psram); -static void record_deep_copy(heap_trace_record_t *rDest, const heap_trace_record_t* r_src); +static void record_deep_copy(heap_trace_record_t *r_dest, const heap_trace_record_t *r_src); static void list_setup(void); -static void list_remove(heap_trace_record_t* r_remove); -static bool list_add(const heap_trace_record_t* r_append); +static void list_remove(heap_trace_record_t *r_remove); +static heap_trace_record_t* list_add(const heap_trace_record_t *r_append); static heap_trace_record_t* list_pop_unused(void); -static heap_trace_record_t* list_find_address_reverse(void* p); +static heap_trace_record_t* list_find_address_reverse(void *p); +static void map_add(const heap_trace_record_t *r_add); +static void map_remove(void *p); +static heap_trace_record_t* map_find(void *p); /* The actual records. */ static records_t records; +/* The hashmap */ +static hashmap_t map; + /* Actual number of allocations logged */ static size_t total_allocations; /* Actual number of frees logged */ static size_t total_frees; +/* track hits and misses */ +static size_t total_hashmap_hits; +static size_t total_hashmap_miss; + /* Used to speed up heap_trace_get */ static heap_trace_record_t* r_get; static size_t r_get_idx; @@ -94,6 +113,19 @@ esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t return ESP_OK; } + +esp_err_t heap_trace_set_hashmap(heap_trace_hashmap_entry_t *entries_buffer, size_t num_entries) +{ + if (tracing) { + return ESP_ERR_INVALID_STATE; + } + + map.buffer = entries_buffer; + map.count = num_entries; + + return ESP_OK; +} + esp_err_t heap_trace_start(heap_trace_mode_t mode_param) { if (records.buffer == NULL || records.capacity == 0) { @@ -105,8 +137,11 @@ esp_err_t heap_trace_start(heap_trace_mode_t mode_param) tracing = false; mode = mode_param; - // clear buffer + // clear buffers memset(records.buffer, 0, sizeof(heap_trace_record_t) * records.capacity); + if (map.buffer) { + memset(map.buffer, 0, sizeof(heap_trace_hashmap_entry_t) * map.count); + } records.count = 0; records.has_overflowed = false; @@ -114,6 +149,8 @@ esp_err_t heap_trace_start(heap_trace_mode_t mode_param) total_allocations = 0; total_frees = 0; + total_hashmap_hits = 0; + total_hashmap_miss = 0; heap_trace_resume(); portEXIT_CRITICAL(&trace_mux); @@ -183,16 +220,10 @@ esp_err_t heap_trace_get(size_t index, heap_trace_record_t *r_out) } } - // copy to destination - if (r_get) { - memcpy(r_out, r_get, sizeof(heap_trace_record_t)); - } else { - // this should not happen since we already - // checked that index < records.count, - // but could be indicative of memory corruption - result = ESP_ERR_INVALID_STATE; - memset(r_out, 0, sizeof(heap_trace_record_t)); - } + // We already checked that index < records.count, + // This could be indicative of memory corruption. + assert(r_get != NULL); + memcpy(r_out, r_get, sizeof(heap_trace_record_t)); } portEXIT_CRITICAL(&trace_mux); @@ -213,6 +244,8 @@ esp_err_t heap_trace_summary(heap_trace_summary_t *summary) summary->capacity = records.capacity; summary->high_water_mark = records.high_water_mark; summary->has_overflowed = records.has_overflowed; + summary->total_hashmap_hits = total_hashmap_hits; + summary->total_hashmap_miss = total_hashmap_miss; portEXIT_CRITICAL(&trace_mux); return ESP_OK; @@ -239,53 +272,53 @@ static void heap_trace_dump_base(bool internal_ram, bool psram) // Iterate through through the linked list - heap_trace_record_t *rCur = TAILQ_FIRST(&records.list); + heap_trace_record_t *r_cur = TAILQ_FIRST(&records.list); for (int i = 0; i < records.count; i++) { // check corruption - if (rCur == NULL) { + if (r_cur == NULL) { esp_rom_printf("\nError: heap trace linked list is corrupt. expected more records.\n"); break; } - bool should_print = rCur->address != NULL && + bool should_print = r_cur->address != NULL && ((psram && internal_ram) || - (internal_ram && esp_ptr_internal(rCur->address)) || - (psram && esp_ptr_external_ram(rCur->address))); + (internal_ram && esp_ptr_internal(r_cur->address)) || + (psram && esp_ptr_external_ram(r_cur->address))); if (should_print) { const char* label = ""; - if (esp_ptr_internal(rCur->address)) { + if (esp_ptr_internal(r_cur->address)) { label = ", Internal"; } - if (esp_ptr_external_ram(rCur->address)) { + if (esp_ptr_external_ram(r_cur->address)) { label = ", PSRAM"; } esp_rom_printf("%6d bytes (@ %p%s) allocated CPU %d ccount 0x%08x caller ", - rCur->size, rCur->address, label, rCur->ccount & 1, rCur->ccount & ~3); + r_cur->size, r_cur->address, label, r_cur->ccount & 1, r_cur->ccount & ~3); - for (int j = 0; j < STACK_DEPTH && rCur->alloced_by[j] != 0; j++) { - esp_rom_printf("%p%s", rCur->alloced_by[j], + for (int j = 0; j < STACK_DEPTH && r_cur->alloced_by[j] != 0; j++) { + esp_rom_printf("%p%s", r_cur->alloced_by[j], (j < STACK_DEPTH - 1) ? ":" : ""); } - if (mode != HEAP_TRACE_ALL || STACK_DEPTH == 0 || rCur->freed_by[0] == NULL) { - delta_size += rCur->size; + if (mode != HEAP_TRACE_ALL || STACK_DEPTH == 0 || r_cur->freed_by[0] == NULL) { + delta_size += r_cur->size; delta_allocs++; esp_rom_printf("\n"); } else { esp_rom_printf("\nfreed by "); for (int j = 0; j < STACK_DEPTH; j++) { - esp_rom_printf("%p%s", rCur->freed_by[j], + esp_rom_printf("%p%s", r_cur->freed_by[j], (j < STACK_DEPTH - 1) ? ":" : "\n"); } } } - rCur = TAILQ_NEXT(rCur, tailq); + r_cur = TAILQ_NEXT(r_cur, tailq); } esp_rom_printf("====== Heap Trace Summary ======\n"); @@ -302,6 +335,9 @@ static void heap_trace_dump_base(bool internal_ram, bool psram) esp_rom_printf("records: %u (%u capacity, %u high water mark)\n", records.count, records.capacity, records.high_water_mark); + esp_rom_printf("hashmap: %u capacity (%u hits, %u misses)\n", + map.count, total_hashmap_hits, total_hashmap_miss); + esp_rom_printf("total allocations: %u\n", total_allocations); esp_rom_printf("total frees: %u\n", total_frees); @@ -317,9 +353,9 @@ static void heap_trace_dump_base(bool internal_ram, bool psram) } /* Add a new allocation to the heap trace records */ -static IRAM_ATTR void record_allocation(const heap_trace_record_t *rAllocation) +static IRAM_ATTR void record_allocation(const heap_trace_record_t *r_allocation) { - if (!tracing || rAllocation->address == NULL) { + if (!tracing || r_allocation->address == NULL) { return; } @@ -333,13 +369,19 @@ static IRAM_ATTR void record_allocation(const heap_trace_record_t *rAllocation) records.has_overflowed = true; - heap_trace_record_t *rFirst = TAILQ_FIRST(&records.list); + heap_trace_record_t *r_first = TAILQ_FIRST(&records.list); - list_remove(rFirst); + list_remove(r_first); + map_remove(r_first->address); } // push onto end of list - list_add(rAllocation); + heap_trace_record_t *r_dest = list_add(r_allocation); + + // add to hashmap + if (r_dest) { + map_add(r_dest); + } total_allocations++; } @@ -367,20 +409,28 @@ static IRAM_ATTR void record_free(void *p, void **callers) total_frees++; - // search backwards for the allocation record matching this free - heap_trace_record_t* rFound = list_find_address_reverse(p); + // check the hashmap + heap_trace_record_t *r_found = map_find(p); - if (rFound) { + // list search + if(!r_found) { + r_found = list_find_address_reverse(p); + } + + // search backwards for the allocation record matching this fre + + if (r_found) { if (mode == HEAP_TRACE_ALL) { // add 'freed_by' info to the record - memcpy(rFound->freed_by, callers, sizeof(void *) * STACK_DEPTH); + memcpy(r_found->freed_by, callers, sizeof(void *) * STACK_DEPTH); } else { // HEAP_TRACE_LEAKS // Leak trace mode, once an allocation is freed - // we remove it from the list - list_remove(rFound); + // we remove it from the list & hashmap + list_remove(r_found); + map_remove(p); } } } @@ -396,15 +446,15 @@ static void list_setup(void) for (int i = 0; i < records.capacity; i++) { - heap_trace_record_t* rCur = &records.buffer[i]; + heap_trace_record_t *r_cur = &records.buffer[i]; - TAILQ_INSERT_TAIL(&records.unused, rCur, tailq); + TAILQ_INSERT_TAIL(&records.unused, r_cur, tailq); } } /* 1. removes record r_remove from records.list, 2. places it into records.unused */ -static IRAM_ATTR void list_remove(heap_trace_record_t* r_remove) +static IRAM_ATTR void list_remove(heap_trace_record_t *r_remove) { assert(records.count > 0); @@ -432,45 +482,45 @@ static IRAM_ATTR heap_trace_record_t* list_pop_unused(void) } // get from records.unused - heap_trace_record_t* rUnused = TAILQ_FIRST(&records.unused); - assert(rUnused->address == NULL); - assert(rUnused->size == 0); + heap_trace_record_t *r_unused = TAILQ_FIRST(&records.unused); + assert(r_unused->address == NULL); + assert(r_unused->size == 0); // remove from records.unused - TAILQ_REMOVE(&records.unused, rUnused, tailq); + TAILQ_REMOVE(&records.unused, r_unused, tailq); - return rUnused; + return r_unused; } // deep copy a record. // Note: only copies the *allocation data*, not the next & prev ptrs -static IRAM_ATTR void record_deep_copy(heap_trace_record_t *rDest, const heap_trace_record_t *r_src) +static IRAM_ATTR void record_deep_copy(heap_trace_record_t *r_dest, const heap_trace_record_t *r_src) { - rDest->ccount = r_src->ccount; - rDest->address = r_src->address; - rDest->size = r_src->size; - memcpy(rDest->freed_by, r_src->freed_by, sizeof(void *) * STACK_DEPTH); - memcpy(rDest->alloced_by, r_src->alloced_by, sizeof(void *) * STACK_DEPTH); + r_dest->ccount = r_src->ccount; + r_dest->address = r_src->address; + r_dest->size = r_src->size; + memcpy(r_dest->freed_by, r_src->freed_by, sizeof(void *) * STACK_DEPTH); + memcpy(r_dest->alloced_by, r_src->alloced_by, sizeof(void *) * STACK_DEPTH); } // Append a record to records.list // Note: This deep copies r_append -static IRAM_ATTR bool list_add(const heap_trace_record_t *r_append) +static IRAM_ATTR heap_trace_record_t* list_add(const heap_trace_record_t *r_append) { if (records.count < records.capacity) { // get unused record - heap_trace_record_t* rDest = list_pop_unused(); + heap_trace_record_t *r_dest = list_pop_unused(); // we checked that there is capacity, so this // should never be null. - assert(rDest != NULL); + assert(r_dest != NULL); // copy allocation data - record_deep_copy(rDest, r_append); + record_deep_copy(r_dest, r_append); // append to records.list - TAILQ_INSERT_TAIL(&records.list, rDest, tailq); + TAILQ_INSERT_TAIL(&records.list, r_dest, tailq); // increment records.count++; @@ -480,34 +530,102 @@ static IRAM_ATTR bool list_add(const heap_trace_record_t *r_append) records.high_water_mark = records.count; } - return true; + return r_dest; } else { records.has_overflowed = true; - return false; + return NULL; } } // search records.list backwards for the allocation record matching this address -static IRAM_ATTR heap_trace_record_t* list_find_address_reverse(void* p) +static IRAM_ATTR heap_trace_record_t* list_find_address_reverse(void *p) { if (records.count == 0) { return NULL; } - heap_trace_record_t* rFound = NULL; + heap_trace_record_t *r_found = NULL; // Perf: We search backwards because new allocations are appended // to the end of the list and most allocations are short lived. - heap_trace_record_t *rCur = NULL; - TAILQ_FOREACH_REVERSE(rCur, &records.list, heap_trace_record_list_struct_t, tailq) { - if (rCur->address == p) { - rFound = rCur; + heap_trace_record_t *r_cur = NULL; + TAILQ_FOREACH_REVERSE(r_cur, &records.list, heap_trace_record_list_struct_t, tailq) { + if (r_cur->address == p) { + r_found = r_cur; break; } } - return rFound; + return r_found; +} + +#define MAXLINEAR 100 + +static size_t hash_idx(void* p) +{ + const uint64_t prime = 11020851777194292899ULL; + uint32_t n = (uint32_t) p; + return (n * prime) % map.count; +} + +static void map_add(const heap_trace_record_t *r_add) +{ + if (map.buffer == NULL || map.count == 0) { + return; + } + + size_t idx = hash_idx(r_add->address); + + // linear search: find empty location + for(size_t i = 0; i < MAXLINEAR; i++) { + size_t n = (i + idx) % map.count; + if (map.buffer[n].address == NULL) { + map.buffer[n].address = r_add->address; + map.buffer[n].record = (heap_trace_record_t*) r_add; + break; + } + } +} + +static void map_remove(void *p) +{ + if (map.buffer == NULL || map.count == 0) { + return; + } + + size_t idx = hash_idx(p); + + // linear search: find matching address + for(size_t i = 0; i < MAXLINEAR; i++) { + size_t n = (i + idx) % map.count; + if (map.buffer[n].address == p) { + map.buffer[n].address = NULL; + map.buffer[n].record = NULL; + break; + } + } +} + +static heap_trace_record_t* map_find(void *p) +{ + if (map.buffer == NULL || map.count == 0) { + return NULL; + } + + size_t idx = hash_idx(p); + + // linear search: find matching address + for(size_t i = 0; i < MAXLINEAR; i++) { + size_t n = (i + idx) % map.count; + if (map.buffer[n].address == p) { + total_hashmap_hits++; + return map.buffer[n].record; + } + } + + total_hashmap_miss++; + return NULL; } #include "heap_trace.inc" diff --git a/components/heap/include/esp_heap_trace.h b/components/heap/include/esp_heap_trace.h index b1c5d476e4c..34378e8302b 100644 --- a/components/heap/include/esp_heap_trace.h +++ b/components/heap/include/esp_heap_trace.h @@ -41,6 +41,11 @@ typedef struct heap_trace_record_t { #endif // CONFIG_HEAP_TRACING_STANDALONE } heap_trace_record_t; +typedef struct heap_trace_hashmap_entry_t { + void* address; ///< ptr returned by malloc/calloc/realloc + heap_trace_record_t* record; ///< associated record +} heap_trace_hashmap_entry_t; + /** * @brief Stores information about the result of a heap trace. */ @@ -52,6 +57,8 @@ typedef struct { size_t capacity; ///< The capacity of the internal buffer size_t high_water_mark; ///< The maximum value that 'count' got to size_t has_overflowed; ///< True if the internal buffer overflowed at some point + size_t total_hashmap_hits; ///< If hashmap is used, the total number of hits + size_t total_hashmap_miss; ///< If hashmap is used, the total number of misses (possibly due to overflow) } heap_trace_summary_t; /** @@ -71,6 +78,22 @@ typedef struct { */ esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records); + +/** + * @brief Provide a hashmap to greatly improve the performance of standalone heap trace leaks mode. + * + * This function must be called before heap_trace_start. + * + * @param entries_buffer Provide a buffer to use for heap trace hashmap. + * Note: External RAM is allowed, but it prevents recording allocations made from ISR's. + * @param num_entries Size of the entries_buffer. Should be greater than num_records, preferably 2-4x as large. + * @return + * - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig. + * - ESP_ERR_INVALID_STATE Heap tracing is currently in progress. + * - ESP_OK Heap tracing initialised successfully. + */ +esp_err_t heap_trace_set_hashmap(heap_trace_hashmap_entry_t *entries_buffer, size_t num_entries); + /** * @brief Initialise heap tracing in host-based mode. * From bdfc348ab3c0efa3d32643e5cabdc3f9b2c9c75b Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Wed, 1 Mar 2023 12:42:14 +0100 Subject: [PATCH 2/3] heap: Add trace hash map config - and place all added functions and vairables related to the hashmap in RAM when the config is enabled only. - add number of hash map entry as a Kconfig value and remove the hash map init function. This prevents the user from allocating the hash map in flash and pass the pointer to the init function (as the heap trace manipulate the hash map from functions placed in IRAM). - add max linear value to the KConfig to make it configurable by the users. - protect access to static variable "tracing" - remove unecessary field in heap_trace_hashmap_entry_t --- components/heap/Kconfig | 35 ++++ components/heap/heap_trace_standalone.c | 243 +++++++++++------------ components/heap/include/esp_heap_trace.h | 23 +-- 3 files changed, 152 insertions(+), 149 deletions(-) diff --git a/components/heap/Kconfig b/components/heap/Kconfig index 5f10676bc95..652e8c3324f 100644 --- a/components/heap/Kconfig +++ b/components/heap/Kconfig @@ -70,6 +70,41 @@ menu "Heap memory debugging" This function depends on heap poisoning being enabled and adds four more bytes of overhead for each block allocated. + config HEAP_TRACE_HASH_MAP + bool "Use hash map mechanism to access heap trace records" + depends on HEAP_TRACING_STANDALONE + default n + help + Enable this flag to use a hash map to increase performance in handling + heap trace records. + + Keeping this as "n" in your project will save RAM and heap memory but will lower + the performance of the heap trace in adding, retrieving and removing trace records. + Making this as "y" in your project, you will decrease free RAM and heap memory but, + the heap trace performances in adding retrieving and removing trace records will be + enhanced. + + config HEAP_TRACE_HASH_MAP_SIZE + int "The number of entries in the hash map" + depends on HEAP_TRACE_HASH_MAP + range 10 10000 + default 100 + help + Defines the number of entries in the heap trace hashmap. The bigger this number is, + the bigger the hash map will be in the memory and the lower the miss probability will + be when handling heap trace records. + + config HEAP_TRACE_HASH_MAP_MAX_LINEAR + int "The number of iterations when searching for an entry in the hash map." + depends on HEAP_TRACE_HASH_MAP + range 1 HEAP_TRACE_HASH_MAP_SIZE + default 10 + help + Defines the number of iterations when searching for an entry in the hashmap. The closer + this number is from the number of entries in the hashmap, the lower the miss chances are + but the slower the hashmap mechanism is. The lower this number is, the higher the miss + probability is but the faster the hashmap mechanism is. + config HEAP_ABORT_WHEN_ALLOCATION_FAILS bool "Abort if memory allocation fails" default n diff --git a/components/heap/heap_trace_standalone.c b/components/heap/heap_trace_standalone.c index 4ce549308d7..9e27d10b28f 100644 --- a/components/heap/heap_trace_standalone.c +++ b/components/heap/heap_trace_standalone.c @@ -5,6 +5,7 @@ */ #include #include +#include #define HEAP_TRACE_SRCFILE /* don't warn on inclusion here */ #include "esp_heap_trace.h" @@ -56,15 +57,6 @@ typedef struct { bool has_overflowed; } records_t; -typedef struct { - - /* Buffer used for hashmap entries */ - heap_trace_hashmap_entry_t *buffer; - - /* length of 'buffer' */ - size_t count; -} hashmap_t; - // Forward Defines static void heap_trace_dump_base(bool internal_ram, bool psram); static void record_deep_copy(heap_trace_record_t *r_dest, const heap_trace_record_t *r_src); @@ -73,30 +65,82 @@ static void list_remove(heap_trace_record_t *r_remove); static heap_trace_record_t* list_add(const heap_trace_record_t *r_append); static heap_trace_record_t* list_pop_unused(void); static heap_trace_record_t* list_find_address_reverse(void *p); -static void map_add(const heap_trace_record_t *r_add); -static void map_remove(void *p); -static heap_trace_record_t* map_find(void *p); /* The actual records. */ static records_t records; -/* The hashmap */ -static hashmap_t map; - /* Actual number of allocations logged */ static size_t total_allocations; /* Actual number of frees logged */ static size_t total_frees; -/* track hits and misses */ -static size_t total_hashmap_hits; -static size_t total_hashmap_miss; - /* Used to speed up heap_trace_get */ static heap_trace_record_t* r_get; static size_t r_get_idx; +#if CONFIG_HEAP_TRACE_HASH_MAP + +typedef struct heap_trace_hashmap_entry_t { + heap_trace_record_t* record; // associated record +} heap_trace_hashmap_entry_t; + +static heap_trace_hashmap_entry_t hash_map[(size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE]; // Buffer used for hashmap entries +static size_t total_hashmap_hits; +static size_t total_hashmap_miss; + +static size_t hash_idx(void* p) +{ + static const uint32_t fnv_prime = 16777619UL; // expression 224 + 28 + 0x93 (32 bits size) + return ((uint32_t)p * fnv_prime) % (uint32_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; +} + +static void map_add(const heap_trace_record_t *r_add) +{ + size_t idx = hash_idx(r_add->address); + + // linear search: find empty location + for(size_t i = 0; i < CONFIG_HEAP_TRACE_HASH_MAP_MAX_LINEAR; i++) { + size_t n = (i + idx) % (size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; + if (hash_map[n].record == NULL) { + hash_map[n].record = (heap_trace_record_t*) r_add; + break; + } + } +} + +static void map_remove(void *p) +{ + size_t idx = hash_idx(p); + + // linear search: find matching address + for(size_t i = 0; i < CONFIG_HEAP_TRACE_HASH_MAP_MAX_LINEAR; i++) { + size_t n = (i + idx) % (size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; + if (hash_map[n].record != NULL && hash_map[n].record->address == p) { + hash_map[n].record = NULL; + break; + } + } +} + +static heap_trace_record_t* map_find(void *p) +{ + size_t idx = hash_idx(p); + + // linear search: find matching address + for(size_t i = 0; i < CONFIG_HEAP_TRACE_HASH_MAP_MAX_LINEAR; i++) { + size_t n = (i + idx) % (size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; + if (hash_map[n].record != NULL && hash_map[n].record->address == p) { + total_hashmap_hits++; + return hash_map[n].record; + } + } + + total_hashmap_miss++; + return NULL; +} +#endif // CONFIG_HEAP_TRACE_HASH_MAP + esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records) { if (tracing) { @@ -113,16 +157,12 @@ esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t return ESP_OK; } - -esp_err_t heap_trace_set_hashmap(heap_trace_hashmap_entry_t *entries_buffer, size_t num_entries) +static esp_err_t set_tracing(bool enable) { - if (tracing) { + if (tracing == enable) { return ESP_ERR_INVALID_STATE; } - - map.buffer = entries_buffer; - map.count = num_entries; - + tracing = enable; return ESP_OK; } @@ -134,14 +174,17 @@ esp_err_t heap_trace_start(heap_trace_mode_t mode_param) portENTER_CRITICAL(&trace_mux); - tracing = false; + set_tracing(false); mode = mode_param; // clear buffers memset(records.buffer, 0, sizeof(heap_trace_record_t) * records.capacity); - if (map.buffer) { - memset(map.buffer, 0, sizeof(heap_trace_hashmap_entry_t) * map.count); - } + +#if CONFIG_HEAP_TRACE_HASH_MAP + memset(hash_map, 0, sizeof(hash_map)); + total_hashmap_hits = 0; + total_hashmap_miss = 0; +#endif // CONFIG_HEAP_TRACE_HASH_MAP records.count = 0; records.has_overflowed = false; @@ -149,31 +192,27 @@ esp_err_t heap_trace_start(heap_trace_mode_t mode_param) total_allocations = 0; total_frees = 0; - total_hashmap_hits = 0; - total_hashmap_miss = 0; - heap_trace_resume(); - portEXIT_CRITICAL(&trace_mux); - return ESP_OK; -} + const esp_err_t ret_val = set_tracing(true); -static esp_err_t set_tracing(bool enable) -{ - if (tracing == enable) { - return ESP_ERR_INVALID_STATE; - } - tracing = enable; - return ESP_OK; + portEXIT_CRITICAL(&trace_mux); + return ret_val; } esp_err_t heap_trace_stop(void) { - return set_tracing(false); + portENTER_CRITICAL(&trace_mux); + const esp_err_t ret_val = set_tracing(false); + portEXIT_CRITICAL(&trace_mux); + return ret_val; } esp_err_t heap_trace_resume(void) { - return set_tracing(true); + portENTER_CRITICAL(&trace_mux); + const esp_err_t ret_val = set_tracing(true); + portEXIT_CRITICAL(&trace_mux); + return ret_val; } size_t heap_trace_get_count(void) @@ -244,8 +283,10 @@ esp_err_t heap_trace_summary(heap_trace_summary_t *summary) summary->capacity = records.capacity; summary->high_water_mark = records.high_water_mark; summary->has_overflowed = records.has_overflowed; +#if CONFIG_HEAP_TRACE_HASH_MAP summary->total_hashmap_hits = total_hashmap_hits; summary->total_hashmap_miss = total_hashmap_miss; +#endif // CONFIG_HEAP_TRACE_HASH_MAP portEXIT_CRITICAL(&trace_mux); return ESP_OK; @@ -267,7 +308,7 @@ static void heap_trace_dump_base(bool internal_ram, bool psram) size_t delta_allocs = 0; size_t start_count = records.count; - esp_rom_printf("====== Heap Trace: %u records (%u capacity) ======\n", + esp_rom_printf("====== Heap Trace: %"PRIu32" records (%"PRIu32" capacity) ======\n", records.count, records.capacity); // Iterate through through the linked list @@ -325,21 +366,23 @@ static void heap_trace_dump_base(bool internal_ram, bool psram) if (mode == HEAP_TRACE_ALL) { esp_rom_printf("Mode: Heap Trace All\n"); - esp_rom_printf("%u bytes alive in trace (%u/%u allocations)\n", + esp_rom_printf("%"PRIu32" bytes alive in trace (%"PRIu32"/%"PRIu32" allocations)\n", delta_size, delta_allocs, heap_trace_get_count()); } else { esp_rom_printf("Mode: Heap Trace Leaks\n"); - esp_rom_printf("%u bytes 'leaked' in trace (%u allocations)\n", delta_size, delta_allocs); + esp_rom_printf("%"PRIu32" bytes 'leaked' in trace (%"PRIu32" allocations)\n", delta_size, delta_allocs); } - esp_rom_printf("records: %u (%u capacity, %u high water mark)\n", + esp_rom_printf("records: %"PRIu32" (%"PRIu32" capacity, %"PRIu32" high water mark)\n", records.count, records.capacity, records.high_water_mark); - esp_rom_printf("hashmap: %u capacity (%u hits, %u misses)\n", - map.count, total_hashmap_hits, total_hashmap_miss); +#if CONFIG_HEAP_TRACE_HASH_MAP + esp_rom_printf("hashmap: %"PRIu32" capacity (%"PRIu32" hits, %"PRIu32" misses)\n", + (size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE, total_hashmap_hits, total_hashmap_miss); +#endif // CONFIG_HEAP_TRACE_HASH_MAP - esp_rom_printf("total allocations: %u\n", total_allocations); - esp_rom_printf("total frees: %u\n", total_frees); + esp_rom_printf("total allocations: %"PRIu32"\n", total_allocations); + esp_rom_printf("total frees: %"PRIu32"\n", total_frees); if (start_count != records.count) { // only a problem if trace isn't stopped before dumping esp_rom_printf("(NB: New entries were traced while dumping, so trace dump may have duplicate entries.)\n"); @@ -372,16 +415,20 @@ static IRAM_ATTR void record_allocation(const heap_trace_record_t *r_allocation) heap_trace_record_t *r_first = TAILQ_FIRST(&records.list); list_remove(r_first); +#if CONFIG_HEAP_TRACE_HASH_MAP map_remove(r_first->address); } - // push onto end of list heap_trace_record_t *r_dest = list_add(r_allocation); - // add to hashmap if (r_dest) { map_add(r_dest); } +#else + } + // push onto end of list + list_add(r_allocation); +#endif // CONFIG_HEAP_TRACE_HASH_MAP total_allocations++; } @@ -404,11 +451,19 @@ static IRAM_ATTR void record_free(void *p, void **callers) } portENTER_CRITICAL(&trace_mux); + // return directly if records.count == 0. In case of hashmap being used + // this prevents the hashmap to return an item that is no longer in the + // records list. + if (records.count == 0) { + portEXIT_CRITICAL(&trace_mux); + return; + } if (tracing) { total_frees++; +#if CONFIG_HEAP_TRACE_HASH_MAP // check the hashmap heap_trace_record_t *r_found = map_find(p); @@ -416,7 +471,9 @@ static IRAM_ATTR void record_free(void *p, void **callers) if(!r_found) { r_found = list_find_address_reverse(p); } - +#else + heap_trace_record_t *r_found = list_find_address_reverse(p); +#endif // CONFIG_HEAP_TRACE_HASH_MAP // search backwards for the allocation record matching this fre if (r_found) { @@ -430,7 +487,9 @@ static IRAM_ATTR void record_free(void *p, void **callers) // Leak trace mode, once an allocation is freed // we remove it from the list & hashmap list_remove(r_found); +#if CONFIG_HEAP_TRACE_HASH_MAP map_remove(p); +#endif // CONFIG_HEAP_TRACE_HASH_MAP } } } @@ -541,10 +600,6 @@ static IRAM_ATTR heap_trace_record_t* list_add(const heap_trace_record_t *r_appe // search records.list backwards for the allocation record matching this address static IRAM_ATTR heap_trace_record_t* list_find_address_reverse(void *p) { - if (records.count == 0) { - return NULL; - } - heap_trace_record_t *r_found = NULL; // Perf: We search backwards because new allocations are appended @@ -560,74 +615,6 @@ static IRAM_ATTR heap_trace_record_t* list_find_address_reverse(void *p) return r_found; } -#define MAXLINEAR 100 - -static size_t hash_idx(void* p) -{ - const uint64_t prime = 11020851777194292899ULL; - uint32_t n = (uint32_t) p; - return (n * prime) % map.count; -} - -static void map_add(const heap_trace_record_t *r_add) -{ - if (map.buffer == NULL || map.count == 0) { - return; - } - - size_t idx = hash_idx(r_add->address); - - // linear search: find empty location - for(size_t i = 0; i < MAXLINEAR; i++) { - size_t n = (i + idx) % map.count; - if (map.buffer[n].address == NULL) { - map.buffer[n].address = r_add->address; - map.buffer[n].record = (heap_trace_record_t*) r_add; - break; - } - } -} - -static void map_remove(void *p) -{ - if (map.buffer == NULL || map.count == 0) { - return; - } - - size_t idx = hash_idx(p); - - // linear search: find matching address - for(size_t i = 0; i < MAXLINEAR; i++) { - size_t n = (i + idx) % map.count; - if (map.buffer[n].address == p) { - map.buffer[n].address = NULL; - map.buffer[n].record = NULL; - break; - } - } -} - -static heap_trace_record_t* map_find(void *p) -{ - if (map.buffer == NULL || map.count == 0) { - return NULL; - } - - size_t idx = hash_idx(p); - - // linear search: find matching address - for(size_t i = 0; i < MAXLINEAR; i++) { - size_t n = (i + idx) % map.count; - if (map.buffer[n].address == p) { - total_hashmap_hits++; - return map.buffer[n].record; - } - } - - total_hashmap_miss++; - return NULL; -} - #include "heap_trace.inc" #endif // CONFIG_HEAP_TRACING_STANDALONE diff --git a/components/heap/include/esp_heap_trace.h b/components/heap/include/esp_heap_trace.h index 34378e8302b..ff7b2f6ef54 100644 --- a/components/heap/include/esp_heap_trace.h +++ b/components/heap/include/esp_heap_trace.h @@ -41,11 +41,6 @@ typedef struct heap_trace_record_t { #endif // CONFIG_HEAP_TRACING_STANDALONE } heap_trace_record_t; -typedef struct heap_trace_hashmap_entry_t { - void* address; ///< ptr returned by malloc/calloc/realloc - heap_trace_record_t* record; ///< associated record -} heap_trace_hashmap_entry_t; - /** * @brief Stores information about the result of a heap trace. */ @@ -57,8 +52,10 @@ typedef struct { size_t capacity; ///< The capacity of the internal buffer size_t high_water_mark; ///< The maximum value that 'count' got to size_t has_overflowed; ///< True if the internal buffer overflowed at some point +#if CONFIG_HEAP_TRACE_HASH_MAP size_t total_hashmap_hits; ///< If hashmap is used, the total number of hits size_t total_hashmap_miss; ///< If hashmap is used, the total number of misses (possibly due to overflow) +#endif } heap_trace_summary_t; /** @@ -78,22 +75,6 @@ typedef struct { */ esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records); - -/** - * @brief Provide a hashmap to greatly improve the performance of standalone heap trace leaks mode. - * - * This function must be called before heap_trace_start. - * - * @param entries_buffer Provide a buffer to use for heap trace hashmap. - * Note: External RAM is allowed, but it prevents recording allocations made from ISR's. - * @param num_entries Size of the entries_buffer. Should be greater than num_records, preferably 2-4x as large. - * @return - * - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig. - * - ESP_ERR_INVALID_STATE Heap tracing is currently in progress. - * - ESP_OK Heap tracing initialised successfully. - */ -esp_err_t heap_trace_set_hashmap(heap_trace_hashmap_entry_t *entries_buffer, size_t num_entries); - /** * @brief Initialise heap tracing in host-based mode. * From 14fa303bbc15d53b1f82d3e05d5d0dcb675313e1 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Thu, 30 Mar 2023 12:37:51 +0200 Subject: [PATCH 3/3] heap: Use linked list in hashmap table to reduce collision, RAM usage and speed up the code --- components/heap/Kconfig | 19 +--- components/heap/heap_trace_standalone.c | 125 +++++++++-------------- components/heap/include/esp_heap_trace.h | 7 +- 3 files changed, 60 insertions(+), 91 deletions(-) diff --git a/components/heap/Kconfig b/components/heap/Kconfig index 652e8c3324f..170e720668a 100644 --- a/components/heap/Kconfig +++ b/components/heap/Kconfig @@ -87,23 +87,12 @@ menu "Heap memory debugging" config HEAP_TRACE_HASH_MAP_SIZE int "The number of entries in the hash map" depends on HEAP_TRACE_HASH_MAP - range 10 10000 - default 100 - help - Defines the number of entries in the heap trace hashmap. The bigger this number is, - the bigger the hash map will be in the memory and the lower the miss probability will - be when handling heap trace records. - - config HEAP_TRACE_HASH_MAP_MAX_LINEAR - int "The number of iterations when searching for an entry in the hash map." - depends on HEAP_TRACE_HASH_MAP - range 1 HEAP_TRACE_HASH_MAP_SIZE + range 1 10000 default 10 help - Defines the number of iterations when searching for an entry in the hashmap. The closer - this number is from the number of entries in the hashmap, the lower the miss chances are - but the slower the hashmap mechanism is. The lower this number is, the higher the miss - probability is but the faster the hashmap mechanism is. + Defines the number of entries in the heap trace hashmap. The bigger this number is, + the bigger the hash map will be in the memory. In case the tracing mode is set to + HEAP_TRACE_ALL, the bigger the hashmap is, the better the performances are. config HEAP_ABORT_WHEN_ALLOCATION_FAILS bool "Abort if memory allocation fails" diff --git a/components/heap/heap_trace_standalone.c b/components/heap/heap_trace_standalone.c index 9e27d10b28f..7c03a9f93e0 100644 --- a/components/heap/heap_trace_standalone.c +++ b/components/heap/heap_trace_standalone.c @@ -81,61 +81,47 @@ static size_t r_get_idx; #if CONFIG_HEAP_TRACE_HASH_MAP -typedef struct heap_trace_hashmap_entry_t { - heap_trace_record_t* record; // associated record -} heap_trace_hashmap_entry_t; +/* Define struct: linked list of records used in hash map */ +TAILQ_HEAD(heap_trace_hash_list_struct_t, heap_trace_record_t); +typedef struct heap_trace_hash_list_struct_t heap_trace_hash_list_t; -static heap_trace_hashmap_entry_t hash_map[(size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE]; // Buffer used for hashmap entries +static heap_trace_hash_list_t hash_map[(size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE]; // Buffer used for hashmap entries static size_t total_hashmap_hits; static size_t total_hashmap_miss; static size_t hash_idx(void* p) { - static const uint32_t fnv_prime = 16777619UL; // expression 224 + 28 + 0x93 (32 bits size) - return ((uint32_t)p * fnv_prime) % (uint32_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; + static const uint32_t fnv_prime = 16777619UL; // expression 2^24 + 2^8 + 0x93 (32 bits size) + // since all the addresses are 4 bytes aligned, computing address * fnv_prime always gives + // a modulo 4 number. The bit shift goal is to distribute more evenly the hashes in the + // given range [0 , CONFIG_HEAP_TRACE_HASH_MAP_SIZE - 1]. + return ((((uint32_t)p >> 3) + + ((uint32_t)p >> 5) + + ((uint32_t)p >> 7)) * fnv_prime) % (uint32_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; } -static void map_add(const heap_trace_record_t *r_add) +static void map_add(heap_trace_record_t *r_add) { size_t idx = hash_idx(r_add->address); - - // linear search: find empty location - for(size_t i = 0; i < CONFIG_HEAP_TRACE_HASH_MAP_MAX_LINEAR; i++) { - size_t n = (i + idx) % (size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; - if (hash_map[n].record == NULL) { - hash_map[n].record = (heap_trace_record_t*) r_add; - break; - } - } + TAILQ_INSERT_TAIL(&hash_map[idx], r_add, tailq_hashmap); } -static void map_remove(void *p) +static void map_remove(heap_trace_record_t *r_remove) { - size_t idx = hash_idx(p); - - // linear search: find matching address - for(size_t i = 0; i < CONFIG_HEAP_TRACE_HASH_MAP_MAX_LINEAR; i++) { - size_t n = (i + idx) % (size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; - if (hash_map[n].record != NULL && hash_map[n].record->address == p) { - hash_map[n].record = NULL; - break; - } - } + size_t idx = hash_idx(r_remove->address); + TAILQ_REMOVE(&hash_map[idx], r_remove, tailq_hashmap); } static heap_trace_record_t* map_find(void *p) { size_t idx = hash_idx(p); - - // linear search: find matching address - for(size_t i = 0; i < CONFIG_HEAP_TRACE_HASH_MAP_MAX_LINEAR; i++) { - size_t n = (i + idx) % (size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; - if (hash_map[n].record != NULL && hash_map[n].record->address == p) { + heap_trace_record_t *r_cur = NULL; + TAILQ_FOREACH(r_cur, &hash_map[idx], tailq_hashmap) { + if (r_cur->address == p) { total_hashmap_hits++; - return hash_map[n].record; + return r_cur; } } - total_hashmap_miss++; return NULL; } @@ -181,7 +167,10 @@ esp_err_t heap_trace_start(heap_trace_mode_t mode_param) memset(records.buffer, 0, sizeof(heap_trace_record_t) * records.capacity); #if CONFIG_HEAP_TRACE_HASH_MAP - memset(hash_map, 0, sizeof(hash_map)); + for (size_t i = 0; i < (size_t)CONFIG_HEAP_TRACE_HASH_MAP_SIZE; i++) { + TAILQ_INIT(&hash_map[i]); + } + total_hashmap_hits = 0; total_hashmap_miss = 0; #endif // CONFIG_HEAP_TRACE_HASH_MAP @@ -239,7 +228,7 @@ esp_err_t heap_trace_get(size_t index, heap_trace_record_t *r_out) // Perf: speed up sequential access if (r_get && r_get_idx == index - 1) { - r_get = TAILQ_NEXT(r_get, tailq); + r_get = TAILQ_NEXT(r_get, tailq_list); r_get_idx = index; } else { @@ -254,7 +243,7 @@ esp_err_t heap_trace_get(size_t index, heap_trace_record_t *r_out) break; } - r_get = TAILQ_NEXT(r_get, tailq); + r_get = TAILQ_NEXT(r_get, tailq_list); r_get_idx = i + 1; } } @@ -359,7 +348,7 @@ static void heap_trace_dump_base(bool internal_ram, bool psram) } } - r_cur = TAILQ_NEXT(r_cur, tailq); + r_cur = TAILQ_NEXT(r_cur, tailq_list); } esp_rom_printf("====== Heap Trace Summary ======\n"); @@ -405,7 +394,6 @@ static IRAM_ATTR void record_allocation(const heap_trace_record_t *r_allocation) portENTER_CRITICAL(&trace_mux); if (tracing) { - // If buffer is full, pop off the oldest // record to make more space if (records.count == records.capacity) { @@ -415,21 +403,9 @@ static IRAM_ATTR void record_allocation(const heap_trace_record_t *r_allocation) heap_trace_record_t *r_first = TAILQ_FIRST(&records.list); list_remove(r_first); -#if CONFIG_HEAP_TRACE_HASH_MAP - map_remove(r_first->address); - } - // push onto end of list - heap_trace_record_t *r_dest = list_add(r_allocation); - // add to hashmap - if (r_dest) { - map_add(r_dest); - } -#else } // push onto end of list list_add(r_allocation); -#endif // CONFIG_HEAP_TRACE_HASH_MAP - total_allocations++; } @@ -446,7 +422,7 @@ static IRAM_ATTR void record_allocation(const heap_trace_record_t *r_allocation) */ static IRAM_ATTR void record_free(void *p, void **callers) { - if (!tracing || p == NULL) { + if (!tracing || p == NULL) { return; } @@ -463,19 +439,7 @@ static IRAM_ATTR void record_free(void *p, void **callers) total_frees++; -#if CONFIG_HEAP_TRACE_HASH_MAP - // check the hashmap - heap_trace_record_t *r_found = map_find(p); - - // list search - if(!r_found) { - r_found = list_find_address_reverse(p); - } -#else heap_trace_record_t *r_found = list_find_address_reverse(p); -#endif // CONFIG_HEAP_TRACE_HASH_MAP - // search backwards for the allocation record matching this fre - if (r_found) { if (mode == HEAP_TRACE_ALL) { @@ -483,13 +447,9 @@ static IRAM_ATTR void record_free(void *p, void **callers) memcpy(r_found->freed_by, callers, sizeof(void *) * STACK_DEPTH); } else { // HEAP_TRACE_LEAKS - // Leak trace mode, once an allocation is freed // we remove it from the list & hashmap list_remove(r_found); -#if CONFIG_HEAP_TRACE_HASH_MAP - map_remove(p); -#endif // CONFIG_HEAP_TRACE_HASH_MAP } } } @@ -507,7 +467,7 @@ static void list_setup(void) heap_trace_record_t *r_cur = &records.buffer[i]; - TAILQ_INSERT_TAIL(&records.unused, r_cur, tailq); + TAILQ_INSERT_TAIL(&records.unused, r_cur, tailq_list); } } @@ -517,15 +477,19 @@ static IRAM_ATTR void list_remove(heap_trace_record_t *r_remove) { assert(records.count > 0); +#if CONFIG_HEAP_TRACE_HASH_MAP + map_remove(r_remove); +#endif + // remove from records.list - TAILQ_REMOVE(&records.list, r_remove, tailq); + TAILQ_REMOVE(&records.list, r_remove, tailq_list); // set as unused r_remove->address = 0; r_remove->size = 0; // add to records.unused - TAILQ_INSERT_HEAD(&records.unused, r_remove, tailq); + TAILQ_INSERT_HEAD(&records.unused, r_remove, tailq_list); // decrement records.count--; @@ -546,7 +510,7 @@ static IRAM_ATTR heap_trace_record_t* list_pop_unused(void) assert(r_unused->size == 0); // remove from records.unused - TAILQ_REMOVE(&records.unused, r_unused, tailq); + TAILQ_REMOVE(&records.unused, r_unused, tailq_list); return r_unused; } @@ -579,7 +543,7 @@ static IRAM_ATTR heap_trace_record_t* list_add(const heap_trace_record_t *r_appe record_deep_copy(r_dest, r_append); // append to records.list - TAILQ_INSERT_TAIL(&records.list, r_dest, tailq); + TAILQ_INSERT_TAIL(&records.list, r_dest, tailq_list); // increment records.count++; @@ -589,6 +553,10 @@ static IRAM_ATTR heap_trace_record_t* list_add(const heap_trace_record_t *r_appe records.high_water_mark = records.count; } +#if CONFIG_HEAP_TRACE_HASH_MAP + map_add(r_dest); +#endif + return r_dest; } else { @@ -602,10 +570,19 @@ static IRAM_ATTR heap_trace_record_t* list_find_address_reverse(void *p) { heap_trace_record_t *r_found = NULL; +#if CONFIG_HEAP_TRACE_HASH_MAP + // check the hashmap + r_found = map_find(p); + + if (r_found != NULL) { + return r_found; + } +#endif + // Perf: We search backwards because new allocations are appended // to the end of the list and most allocations are short lived. heap_trace_record_t *r_cur = NULL; - TAILQ_FOREACH_REVERSE(r_cur, &records.list, heap_trace_record_list_struct_t, tailq) { + TAILQ_FOREACH(r_cur, &records.list, tailq_list) { if (r_cur->address == p) { r_found = r_cur; break; diff --git a/components/heap/include/esp_heap_trace.h b/components/heap/include/esp_heap_trace.h index ff7b2f6ef54..2b0daa2e4c6 100644 --- a/components/heap/include/esp_heap_trace.h +++ b/components/heap/include/esp_heap_trace.h @@ -36,8 +36,11 @@ typedef struct heap_trace_record_t { size_t size; ///< Size of the allocation void *alloced_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which allocated the memory. void *freed_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which freed the memory (all zero if not freed.) -#ifdef CONFIG_HEAP_TRACING_STANDALONE - TAILQ_ENTRY(heap_trace_record_t) tailq; ///< Linked list: prev & next records +#if CONFIG_HEAP_TRACING_STANDALONE + TAILQ_ENTRY(heap_trace_record_t) tailq_list; ///< Linked list: prev & next records +#if CONFIG_HEAP_TRACE_HASH_MAP + TAILQ_ENTRY(heap_trace_record_t) tailq_hashmap; ///< Linked list: prev & next in hashmap entry list +#endif // CONFIG_HEAP_TRACE_HASH_MAP #endif // CONFIG_HEAP_TRACING_STANDALONE } heap_trace_record_t;