diff --git a/include/mimalloc-internal.h b/include/mimalloc-internal.h index 16be12518..4ace8facc 100644 --- a/include/mimalloc-internal.h +++ b/include/mimalloc-internal.h @@ -131,6 +131,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 0b84f6c3a..eeeba6e5c 100644 --- a/include/mimalloc.h +++ b/include/mimalloc.h @@ -303,6 +303,7 @@ typedef enum mi_option_e { // stable options mi_option_show_errors, // print error messages mi_option_show_stats, // print statistics on termination + mi_option_show_histogram, // print histogram mi_option_verbose, // print verbose messages // the following options are experimental (see src/options.h) mi_option_eager_commit, diff --git a/src/alloc.c b/src/alloc.c index cd4afa1ec..be0fa5467 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 90990cd4f..9ee0df19d 100644 --- a/src/init.c +++ b/src/init.c @@ -545,6 +545,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 diff --git a/src/options.c b/src/options.c index c4ed91cba..5745a9002 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..1a5427e50 100644 --- a/src/stats.c +++ b/src/stats.c @@ -451,6 +451,85 @@ 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) +{ + if (mi_unlikely(size == 0)) return; + size_t bucket = mi_bsr(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 // --------------------------------------------------------