Skip to content

Commit 09ad6d2

Browse files
committed
initial statistics api
1 parent 1b749ea commit 09ad6d2

File tree

11 files changed

+365
-164
lines changed

11 files changed

+365
-164
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,7 @@ endif()
641641
install(FILES include/mimalloc.h DESTINATION ${mi_install_incdir})
642642
install(FILES include/mimalloc-override.h DESTINATION ${mi_install_incdir})
643643
install(FILES include/mimalloc-new-delete.h DESTINATION ${mi_install_incdir})
644+
install(FILES include/mimalloc-stats.h DESTINATION ${mi_install_incdir})
644645
install(FILES cmake/mimalloc-config.cmake DESTINATION ${mi_install_cmakedir})
645646
install(FILES cmake/mimalloc-config-version.cmake DESTINATION ${mi_install_cmakedir})
646647

ide/vs2022/mimalloc-lib.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@
486486
<ClInclude Include="$(ProjectDir)..\..\include\mimalloc.h" />
487487
<ClInclude Include="$(ProjectDir)..\..\include\mimalloc-override.h" />
488488
<ClInclude Include="..\..\include\mimalloc-new-delete.h" />
489+
<ClInclude Include="..\..\include\mimalloc-stats.h" />
489490
<ClInclude Include="..\..\include\mimalloc\atomic.h" />
490491
<ClInclude Include="..\..\include\mimalloc\internal.h" />
491492
<ClInclude Include="..\..\include\mimalloc\prim.h" />

ide/vs2022/mimalloc-lib.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@
9393
<ClInclude Include="..\..\include\mimalloc\prim.h">
9494
<Filter>Headers</Filter>
9595
</ClInclude>
96+
<ClInclude Include="..\..\include\mimalloc-stats.h">
97+
<Filter>Headers</Filter>
98+
</ClInclude>
9699
</ItemGroup>
97100
<ItemGroup>
98101
<Filter Include="Headers">

include/mimalloc-stats.h

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* ----------------------------------------------------------------------------
2+
Copyright (c) 2018-2025, Microsoft Research, Daan Leijen
3+
This is free software; you can redistribute it and/or modify it under the
4+
terms of the MIT license. A copy of the license can be found in the file
5+
"LICENSE" at the root of this distribution.
6+
-----------------------------------------------------------------------------*/
7+
#pragma once
8+
#ifndef MIMALLOC_STATS_H
9+
#define MIMALLOC_STATS_H
10+
11+
#include <mimalloc.h>
12+
#include <stdint.h>
13+
14+
#define MI_STAT_VERSION 1 // increased on every backward incompatible change
15+
16+
// count allocation over time
17+
typedef struct mi_stat_count_s {
18+
int64_t total; // total allocated
19+
int64_t peak; // peak allocation
20+
int64_t current; // current allocation
21+
} mi_stat_count_t;
22+
23+
// counters only increase
24+
typedef struct mi_stat_counter_s {
25+
int64_t total; // total count
26+
} mi_stat_counter_t;
27+
28+
#define MI_STAT_FIELDS() \
29+
MI_STAT_COUNT(pages) /* count of mimalloc pages */ \
30+
MI_STAT_COUNT(reserved) /* reserved memory bytes */ \
31+
MI_STAT_COUNT(committed) /* committed bytes */ \
32+
MI_STAT_COUNT(reset) /* reset bytes */ \
33+
MI_STAT_COUNT(purged) /* purged bytes */ \
34+
MI_STAT_COUNT(page_committed) /* committed memory inside pages */ \
35+
MI_STAT_COUNT(pages_abandoned) /* abandonded pages count */ \
36+
MI_STAT_COUNT(threads) /* number of threads */ \
37+
MI_STAT_COUNT(malloc_normal) /* allocated bytes <= MI_LARGE_OBJ_SIZE_MAX */ \
38+
MI_STAT_COUNT(malloc_huge) /* allocated bytes in huge pages */ \
39+
MI_STAT_COUNT(malloc_requested) /* malloc requested bytes */ \
40+
\
41+
MI_STAT_COUNTER(mmap_calls) \
42+
MI_STAT_COUNTER(commit_calls) \
43+
MI_STAT_COUNTER(reset_calls) \
44+
MI_STAT_COUNTER(purge_calls) \
45+
MI_STAT_COUNTER(arena_count) /* number of memory arena's */ \
46+
MI_STAT_COUNTER(malloc_normal_count) /* number of blocks <= MI_LARGE_OBJ_SIZE_MAX */ \
47+
MI_STAT_COUNTER(malloc_huge_count) /* number of huge bloks */ \
48+
MI_STAT_COUNTER(malloc_guarded_count) /* number of allocations with guard pages */ \
49+
\
50+
/* internal statistics */ \
51+
MI_STAT_COUNTER(arena_rollback_count) \
52+
MI_STAT_COUNTER(pages_extended) /* number of page extensions */ \
53+
MI_STAT_COUNTER(pages_retire) /* number of pages that are retired */ \
54+
MI_STAT_COUNTER(page_searches) /* searches for a fresh page */ \
55+
/* only on v1 and v2 */ \
56+
MI_STAT_COUNT(segments) \
57+
MI_STAT_COUNT(segments_abandoned) \
58+
MI_STAT_COUNT(segments_cache) \
59+
MI_STAT_COUNT(_segments_reserved) \
60+
/* only on v3 */ \
61+
MI_STAT_COUNTER(pages_reclaim_on_alloc) \
62+
MI_STAT_COUNTER(pages_reclaim_on_free) \
63+
MI_STAT_COUNTER(pages_reabandon_full) \
64+
MI_STAT_COUNTER(pages_unabandon_busy_wait) \
65+
66+
67+
// Define the statistics structure
68+
#define MI_BIN_HUGE (73U) // see types.h
69+
#define MI_STAT_COUNT(stat) mi_stat_count_t stat;
70+
#define MI_STAT_COUNTER(stat) mi_stat_counter_t stat;
71+
72+
typedef struct mi_stats_s
73+
{
74+
int version;
75+
76+
MI_STAT_FIELDS()
77+
78+
// future extension
79+
mi_stat_count_t _stat_reserved[4];
80+
mi_stat_counter_t _stat_counter_reserved[4];
81+
82+
// size segregated statistics
83+
mi_stat_count_t malloc_bins[MI_BIN_HUGE+1]; // allocation per size bin
84+
mi_stat_count_t page_bins[MI_BIN_HUGE+1]; // pages allocated per size bin
85+
} mi_stats_t;
86+
87+
#undef MI_STAT_COUNT
88+
#undef MI_STAT_COUNTER
89+
90+
// Exported definitions
91+
#ifdef __cplusplus
92+
extern "C" {
93+
#endif
94+
95+
mi_decl_export void mi_stats_get( size_t stats_size, mi_stats_t* stats ) mi_attr_noexcept;
96+
mi_decl_export const char* mi_stats_get_json( size_t buf_size, char* buf ) mi_attr_noexcept; // use mi_free to free the result if the input buf == NULL
97+
98+
#ifdef __cplusplus
99+
}
100+
#endif
101+
102+
#endif // MIMALLOC_STATS_H

include/mimalloc/types.h

Lines changed: 57 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ terms of the MIT license. A copy of the license can be found in the file
2121
// --------------------------------------------------------------------------
2222

2323

24+
#include <mimalloc-stats.h>
2425
#include <stddef.h> // ptrdiff_t
2526
#include <stdint.h> // uintptr_t, uint16_t, etc
2627
#include "atomic.h" // _Atomic
@@ -515,6 +516,61 @@ struct mi_heap_s {
515516
};
516517

517518

519+
// ------------------------------------------------------
520+
// Sub processes do not reclaim or visit segments
521+
// from other sub processes. These are essentially the
522+
// static variables of a process.
523+
// ------------------------------------------------------
524+
525+
struct mi_subproc_s {
526+
_Atomic(size_t) abandoned_count; // count of abandoned segments for this sub-process
527+
_Atomic(size_t) abandoned_os_list_count; // count of abandoned segments in the os-list
528+
mi_lock_t abandoned_os_lock; // lock for the abandoned os segment list (outside of arena's) (this lock protect list operations)
529+
mi_lock_t abandoned_os_visit_lock; // ensure only one thread per subproc visits the abandoned os list
530+
mi_segment_t* abandoned_os_list; // doubly-linked list of abandoned segments outside of arena's (in OS allocated memory)
531+
mi_segment_t* abandoned_os_list_tail; // the tail-end of the list
532+
mi_memid_t memid; // provenance of this memory block
533+
};
534+
535+
536+
// ------------------------------------------------------
537+
// Thread Local data
538+
// ------------------------------------------------------
539+
540+
// Milliseconds as in `int64_t` to avoid overflows
541+
typedef int64_t mi_msecs_t;
542+
543+
// Queue of segments
544+
typedef struct mi_segment_queue_s {
545+
mi_segment_t* first;
546+
mi_segment_t* last;
547+
} mi_segment_queue_t;
548+
549+
// Segments thread local data
550+
typedef struct mi_segments_tld_s {
551+
mi_segment_queue_t small_free; // queue of segments with free small pages
552+
mi_segment_queue_t medium_free; // queue of segments with free medium pages
553+
mi_page_queue_t pages_purge; // queue of freed pages that are delay purged
554+
size_t count; // current number of segments;
555+
size_t peak_count; // peak number of segments
556+
size_t current_size; // current size of all segments
557+
size_t peak_size; // peak size of all segments
558+
size_t reclaim_count;// number of reclaimed (abandoned) segments
559+
mi_subproc_t* subproc; // sub-process this thread belongs to.
560+
mi_stats_t* stats; // points to tld stats
561+
} mi_segments_tld_t;
562+
563+
// Thread local data
564+
struct mi_tld_s {
565+
unsigned long long heartbeat; // monotonic heartbeat count
566+
bool recurse; // true if deferred was called; used to prevent infinite recursion.
567+
mi_heap_t* heap_backing; // backing heap of this thread (cannot be deleted)
568+
mi_heap_t* heaps; // list of heaps in this thread (so we can abandon all when the thread terminates)
569+
mi_segments_tld_t segments; // segment tld
570+
mi_stats_t stats; // statistics
571+
};
572+
573+
518574

519575
// ------------------------------------------------------
520576
// Debug
@@ -550,10 +606,10 @@ void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line
550606
#define mi_assert_expensive(x)
551607
#endif
552608

609+
553610
// ------------------------------------------------------
554611
// Statistics
555612
// ------------------------------------------------------
556-
557613
#ifndef MI_STAT
558614
#if (MI_DEBUG>0)
559615
#define MI_STAT 2
@@ -562,64 +618,6 @@ void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line
562618
#endif
563619
#endif
564620

565-
typedef struct mi_stat_count_s {
566-
int64_t total;
567-
int64_t peak;
568-
int64_t current;
569-
} mi_stat_count_t;
570-
571-
typedef struct mi_stat_counter_s {
572-
int64_t total;
573-
} mi_stat_counter_t;
574-
575-
typedef struct mi_stats_s {
576-
mi_stat_count_t pages; // count of mimalloc pages
577-
mi_stat_count_t reserved; // reserved memory bytes
578-
mi_stat_count_t committed; // committed bytes
579-
mi_stat_count_t reset; // reset bytes
580-
mi_stat_count_t purged; // purged bytes
581-
mi_stat_count_t page_committed; // committed memory inside pages
582-
mi_stat_count_t pages_abandoned; // abandonded pages count
583-
mi_stat_count_t threads; // number of threads
584-
mi_stat_count_t malloc_normal; // allocated bytes <= MI_LARGE_OBJ_SIZE_MAX
585-
mi_stat_count_t malloc_huge; // allocated bytes in huge pages
586-
mi_stat_count_t malloc_requested; // malloc requested bytes
587-
588-
mi_stat_counter_t mmap_calls;
589-
mi_stat_counter_t commit_calls;
590-
mi_stat_counter_t reset_calls;
591-
mi_stat_counter_t purge_calls;
592-
mi_stat_counter_t arena_count; // number of memory arena's
593-
mi_stat_counter_t malloc_normal_count; // number of blocks <= MI_LARGE_OBJ_SIZE_MAX
594-
mi_stat_counter_t malloc_huge_count; // number of huge bloks
595-
mi_stat_counter_t malloc_guarded_count; // number of allocations with guard pages
596-
597-
// internal statistics
598-
mi_stat_counter_t arena_rollback_count;
599-
mi_stat_counter_t pages_extended; // number of page extensions
600-
mi_stat_counter_t pages_retire; // number of pages that are retired
601-
mi_stat_counter_t page_searches; // searches for a fresh page
602-
// only on v1 and v2
603-
mi_stat_count_t segments;
604-
mi_stat_count_t segments_abandoned;
605-
mi_stat_count_t segments_cache;
606-
mi_stat_count_t _segments_reserved;
607-
// only on v3
608-
mi_stat_counter_t pages_reclaim_on_alloc;
609-
mi_stat_counter_t pages_reclaim_on_free;
610-
mi_stat_counter_t pages_reabandon_full;
611-
mi_stat_counter_t pages_unabandon_busy_wait;
612-
613-
// future extension
614-
mi_stat_count_t _stat_reserved[4];
615-
mi_stat_counter_t _stat_counter_reserved[4];
616-
617-
// size segregated statistics
618-
mi_stat_count_t malloc_bins[MI_BIN_HUGE+1]; // allocation per size bin
619-
mi_stat_count_t page_bins[MI_BIN_HUGE+1]; // pages allocated per size bin
620-
} mi_stats_t;
621-
622-
623621
// add to stat keeping track of the peak
624622
void _mi_stat_increase(mi_stat_count_t* stat, size_t amount);
625623
void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount);
@@ -641,56 +639,4 @@ void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount);
641639
#define mi_heap_stat_decrease(heap,stat,amount) mi_stat_decrease( (heap)->tld->stats.stat, amount)
642640

643641

644-
// ------------------------------------------------------
645-
// Sub processes do not reclaim or visit segments
646-
// from other sub processes
647-
// ------------------------------------------------------
648-
649-
struct mi_subproc_s {
650-
_Atomic(size_t) abandoned_count; // count of abandoned segments for this sub-process
651-
_Atomic(size_t) abandoned_os_list_count; // count of abandoned segments in the os-list
652-
mi_lock_t abandoned_os_lock; // lock for the abandoned os segment list (outside of arena's) (this lock protect list operations)
653-
mi_lock_t abandoned_os_visit_lock; // ensure only one thread per subproc visits the abandoned os list
654-
mi_segment_t* abandoned_os_list; // doubly-linked list of abandoned segments outside of arena's (in OS allocated memory)
655-
mi_segment_t* abandoned_os_list_tail; // the tail-end of the list
656-
mi_memid_t memid; // provenance of this memory block
657-
};
658-
659-
// ------------------------------------------------------
660-
// Thread Local data
661-
// ------------------------------------------------------
662-
663-
// Milliseconds as in `int64_t` to avoid overflows
664-
typedef int64_t mi_msecs_t;
665-
666-
// Queue of segments
667-
typedef struct mi_segment_queue_s {
668-
mi_segment_t* first;
669-
mi_segment_t* last;
670-
} mi_segment_queue_t;
671-
672-
// Segments thread local data
673-
typedef struct mi_segments_tld_s {
674-
mi_segment_queue_t small_free; // queue of segments with free small pages
675-
mi_segment_queue_t medium_free; // queue of segments with free medium pages
676-
mi_page_queue_t pages_purge; // queue of freed pages that are delay purged
677-
size_t count; // current number of segments;
678-
size_t peak_count; // peak number of segments
679-
size_t current_size; // current size of all segments
680-
size_t peak_size; // peak size of all segments
681-
size_t reclaim_count;// number of reclaimed (abandoned) segments
682-
mi_subproc_t* subproc; // sub-process this thread belongs to.
683-
mi_stats_t* stats; // points to tld stats
684-
} mi_segments_tld_t;
685-
686-
// Thread local data
687-
struct mi_tld_s {
688-
unsigned long long heartbeat; // monotonic heartbeat count
689-
bool recurse; // true if deferred was called; used to prevent infinite recursion.
690-
mi_heap_t* heap_backing; // backing heap of this thread (cannot be deleted)
691-
mi_heap_t* heaps; // list of heaps in this thread (so we can abandon all when the thread terminates)
692-
mi_segments_tld_t segments; // segment tld
693-
mi_stats_t stats; // statistics
694-
};
695-
696642
#endif

src/heap.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect)
167167

168168
// collect arenas (this is program wide so don't force purges on abandonment of threads)
169169
_mi_arenas_collect(collect == MI_FORCE /* force purge? */);
170+
171+
// merge statistics
172+
if (collect <= MI_FORCE) {
173+
mi_stats_merge();
174+
}
170175
}
171176

172177
void _mi_heap_collect_abandon(mi_heap_t* heap) {

src/init.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ static mi_decl_cache_align mi_tld_t tld_main = {
134134
0, 0, 0, 0, 0, &mi_subproc_default,
135135
&tld_main.stats
136136
}, // segments
137-
{ MI_STATS_NULL } // stats
137+
{ MI_STAT_VERSION, MI_STATS_NULL } // stats
138138
};
139139

140140
mi_decl_cache_align mi_heap_t _mi_heap_main = {
@@ -159,7 +159,7 @@ mi_decl_cache_align mi_heap_t _mi_heap_main = {
159159

160160
bool _mi_process_is_initialized = false; // set to `true` in `mi_process_init`.
161161

162-
mi_stats_t _mi_stats_main = { MI_STATS_NULL };
162+
mi_stats_t _mi_stats_main = { MI_STAT_VERSION, MI_STATS_NULL };
163163

164164
#if MI_GUARDED
165165
mi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed) {

src/page-queue.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,15 @@ static bool mi_heap_contains_queue(const mi_heap_t* heap, const mi_page_queue_t*
136136
}
137137
#endif
138138

139+
static size_t mi_page_bin(const mi_page_t* page) {
140+
const size_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page))));
141+
mi_assert_internal(bin <= MI_BIN_FULL);
142+
return bin;
143+
}
144+
139145
static mi_page_queue_t* mi_heap_page_queue_of(mi_heap_t* heap, const mi_page_t* page) {
140146
mi_assert_internal(heap!=NULL);
141-
size_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page))));
142-
mi_assert_internal(bin <= MI_BIN_FULL);
147+
const size_t bin = mi_page_bin(page);
143148
mi_page_queue_t* pq = &heap->pages[bin];
144149
mi_assert_internal((mi_page_block_size(page) == pq->block_size) ||
145150
(mi_page_is_huge(page) && mi_page_queue_is_huge(pq)) ||

0 commit comments

Comments
 (0)