From b797c72b2f7c8e81ccc765f20ef68eb108c1a17c Mon Sep 17 00:00:00 2001 From: Michael Clark Date: Mon, 17 Jan 2022 10:19:38 +1300 Subject: [PATCH 1/3] histogram: add pow2 allocation stats via MIMALLOC_SHOW_HISTOGRAM=1 --- include/mimalloc-internal.h | 2 + include/mimalloc.h | 1 + src/alloc.c | 9 ++++- src/init.c | 3 ++ src/options.c | 1 + src/stats.c | 78 +++++++++++++++++++++++++++++++++++++ 6 files changed, 93 insertions(+), 1 deletion(-) diff --git a/include/mimalloc-internal.h b/include/mimalloc-internal.h index cf5b6783f..5debd6779 100644 --- a/include/mimalloc-internal.h +++ b/include/mimalloc-internal.h @@ -126,6 +126,8 @@ void _mi_heap_set_default_direct(mi_heap_t* heap); // "stats.c" void _mi_stats_done(mi_stats_t* stats); +void _mi_histogram_log(size_t size); +void _mi_histogram_print(mi_output_fun* out); mi_msecs_t _mi_clock_now(void); mi_msecs_t _mi_clock_end(mi_msecs_t start); diff --git a/include/mimalloc.h b/include/mimalloc.h index 756e516cb..8b7e6a9bb 100644 --- a/include/mimalloc.h +++ b/include/mimalloc.h @@ -303,6 +303,7 @@ typedef enum mi_option_e { // stable options mi_option_show_errors, mi_option_show_stats, + mi_option_show_histogram, mi_option_verbose, // the following options are experimental mi_option_eager_commit, diff --git a/src/alloc.c b/src/alloc.c index d9b6dd609..99bbccfbf 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -98,7 +98,11 @@ extern inline mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexce // The main allocation function extern inline mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept { if (mi_likely(size <= MI_SMALL_SIZE_MAX)) { - return mi_heap_malloc_small(heap, size); + void *p = mi_heap_malloc_small(heap, size); + #if MI_STAT>1 + if (p) { _mi_histogram_log(size); } + #endif + return p; } else { mi_assert(heap!=NULL); @@ -106,6 +110,9 @@ extern inline mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE); // note: size can overflow but it is detected in malloc_generic mi_assert_internal(p == NULL || mi_usable_size(p) >= size); #if MI_STAT>1 + if (p) { _mi_histogram_log(size); } + #endif + #if MI_STAT>1 if (p != NULL) { if (!mi_heap_is_initialized(heap)) { heap = mi_get_default_heap(); } mi_heap_stat_increase(heap, malloc, mi_usable_size(p)); diff --git a/src/init.c b/src/init.c index f26190002..317e8bb90 100644 --- a/src/init.c +++ b/src/init.c @@ -543,6 +543,9 @@ static void mi_process_done(void) { if (mi_option_is_enabled(mi_option_show_stats) || mi_option_is_enabled(mi_option_verbose)) { mi_stats_print(NULL); } + if (mi_option_is_enabled(mi_option_show_histogram)) { + _mi_histogram_print(NULL); + } mi_allocator_done(); _mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id); os_preloading = true; // don't call the C runtime anymore diff --git a/src/options.c b/src/options.c index f9e3984a5..bdf9d36b3 100644 --- a/src/options.c +++ b/src/options.c @@ -63,6 +63,7 @@ static mi_option_desc_t options[_mi_option_last] = { 0, UNINIT, MI_OPTION(show_errors) }, #endif { 0, UNINIT, MI_OPTION(show_stats) }, + { 0, UNINIT, MI_OPTION(show_histogram) }, { 0, UNINIT, MI_OPTION(verbose) }, // the following options are experimental and not all combinations make sense. diff --git a/src/stats.c b/src/stats.c index 6d486f42b..874d2d0b2 100644 --- a/src/stats.c +++ b/src/stats.c @@ -451,6 +451,84 @@ mi_msecs_t _mi_clock_end(mi_msecs_t start) { } +/* ----------------------------------------------------------- + Histogram of allocations sizes (power of 2) +----------------------------------------------------------- */ + +static _Atomic(size_t) mi_hist[MI_SIZE_BITS] = { 0 }; + +void _mi_histogram_log(size_t size) +{ + size_t bucket = MI_SIZE_BITS - 1 - mi_clz(size); + mi_atomic_increment_relaxed(mi_hist + bucket); +} + +static char* _mi_make_bar(char *buf, size_t buflen, size_t value, size_t max, size_t width) +{ + /* we can't dynamically detect if the terminal supports unicode block characters */ + #if defined(_WIN32) + size_t v = value * width / max; + buf[0] = '\0'; + while (v > 0) { + strncat(buf, "*", buflen--); + v--; + } + #else + static const char* a[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" }; + size_t v = value * width * 8 / max; + buf[0] = '\0'; + while (v > 8) { + strncat(buf, a[8], buflen--); + v-=8; + } + strncat(buf, a[v], buflen--); + #endif + return buf; +} + +static char* _mi_format_bytes(char *buf, size_t buflen, size_t count) +{ + #if MI_SIZE_BITS > 32 + if (count & ~((((size_t)1) << 50) - (size_t)1)) { + snprintf(buf, buflen, "%zuPiB", (count+1) >> 50); + } else + if (count & ~((((size_t)1) << 40) - (size_t)1)) { + snprintf(buf, buflen, "%zuTiB", (count+1) >> 40); + } else + #endif + if (count & ~((((size_t)1) << 30) - (size_t)1)) { + snprintf(buf, buflen, "%zuGiB", (count+1) >> 30); + } else + if (count & ~((((size_t)1) << 20) - (size_t)1)) { + snprintf(buf, buflen, "%zuMiB", (count+1) >> 20); + } else + if (count & ~((((size_t)1) << 10) - (size_t)1)) { + snprintf(buf, buflen, "%zuKiB", (count+1) >> 10); + } else { + snprintf(buf, buflen, "%zuB", count); + } + return buf; +} + +void _mi_histogram_print(mi_output_fun* out) +{ + #define _NCOLS 50 + char bar[_NCOLS*3+1], num1[16], num2[16]; + size_t max_allocs = 0; + for (size_t i = 0; i < MI_SIZE_BITS; i++) { + if (mi_hist[i] > max_allocs) { max_allocs = mi_hist[i]; } + } + _mi_fputs(out, NULL, NULL, "\nhistogram of allocation sizes\n"); + for (size_t i = 0; i < MI_SIZE_BITS; i++) { + if (mi_hist[i]) { + _mi_fprintf(out, NULL, "%9s - %-9s [ %-9zu ] %s\n", + _mi_format_bytes(num1, sizeof(num1), ((size_t)1 << i)), + _mi_format_bytes(num2, sizeof(num2), ((size_t)1 << (i+1))-1), + mi_hist[i], _mi_make_bar(bar, sizeof(bar), mi_hist[i], max_allocs, _NCOLS)); + } + } +} + // -------------------------------------------------------- // Basic process statistics // -------------------------------------------------------- From 109ff12cd556e895f2f58d4f3088b6578dfbdbbe Mon Sep 17 00:00:00 2001 From: Michael Clark Date: Thu, 20 Jan 2022 19:34:26 +1300 Subject: [PATCH 2/3] histogram: use mi_bsr as it does the subtraction for us --- src/stats.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stats.c b/src/stats.c index 874d2d0b2..1a5427e50 100644 --- a/src/stats.c +++ b/src/stats.c @@ -459,7 +459,8 @@ static _Atomic(size_t) mi_hist[MI_SIZE_BITS] = { 0 }; void _mi_histogram_log(size_t size) { - size_t bucket = MI_SIZE_BITS - 1 - mi_clz(size); + if (mi_unlikely(size == 0)) return; + size_t bucket = mi_bsr(size); mi_atomic_increment_relaxed(mi_hist + bucket); } From 514a90bac346c87540ad7794219b32c3ca41e32d Mon Sep 17 00:00:00 2001 From: Michael Clark Date: Thu, 20 Jan 2022 19:35:14 +1300 Subject: [PATCH 3/3] histogram: guard _mi_histogram_print with #if MI_STAT>1 otherwise we end up printing a title without any histogram if stats are not enabled. --- src/init.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/init.c b/src/init.c index 317e8bb90..e14c2dd88 100644 --- a/src/init.c +++ b/src/init.c @@ -543,9 +543,11 @@ static void mi_process_done(void) { if (mi_option_is_enabled(mi_option_show_stats) || mi_option_is_enabled(mi_option_verbose)) { mi_stats_print(NULL); } + #if MI_STAT>1 if (mi_option_is_enabled(mi_option_show_histogram)) { _mi_histogram_print(NULL); } + #endif mi_allocator_done(); _mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id); os_preloading = true; // don't call the C runtime anymore