8000 py/gc: Print fragmentation stats in micropython.mem_info(2). · micropython/micropython@8d2b83c · GitHub
[go: up one dir, main page]

Skip to content
< 8000 script crossorigin="anonymous" defer="defer" type="application/javascript" src="https://github.githubassets.com/assets/vendors-node_modules_github_remote-form_dist_index_js-node_modules_delegated-events_dist_inde-94fd67-e789af5a4655.js">

Commit 8d2b83c

Browse files
committed
py/gc: Print fragmentation stats in micropython.mem_info(2).
The previous behavior was (for ports that enable it via `MICROPY_PY_MICROPYTHON_MEM_INFO`), that passing any argument to `micropython.mem_info()` would print out the block table. ``1`` means dump the block table (as before), ``2`` means show the fragmentation stats, i.e. how many allocations of a given size can succeed, ``3`` means both. On small STM32 ports (L0, F0), don't enable support for the full block table, only for the frag stats. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent 7861edd commit 8d2b83c

File tree

12 files changed

+144
-33
lines changed

12 files changed

+144
-33
lines changed

docs/library/micropython.rst

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,23 @@ Functions
5858

5959
.. function:: mem_info([verbose])
6060

61-
Print information about currently used memory. If the *verbose* argument
62-
is given then extra information is printed.
61+
Print information about currently used memory. The information that is
62+
printed is implementation dependent, but always includes the amount of
63+
stack and heap used.
6364

64-
The information that is printed is implementation dependent, but currently
65-
includes the amount of stack and heap used. In verbose mode it prints out
66-
the entire heap indicating which blocks are used and which are free.
65+
The *verbose* argument is a bitfield (defaulting to zero), and will enable
66+
additional information as follows:
67+
68+
If *verbose* is ``1``, then it will additionally print out the entire
69+
heap, showing the status of each block. (This may not be supported on all
70+
boards and ports).
71+
72+
If *verbose* is ``2``, then it will additionally print a summary of
73+
heap fragmentation, showing the number of allocations that would succeed
74+
for successive powers of two sized allocations.
75+
76+
To get both the heap dump and the fragmentation statistics, set *verbose* to
77+
``3``.
6778

6879
.. function:: qstr_info([verbose])
6980

ports/nrf/modules/machine/modmachine.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,12 @@ STATIC mp_obj_t machine_info(mp_uint_t n_args, const mp_obj_t *args) {
131131
printf(" 1=" UINT_FMT " 2=" UINT_FMT " m=" UINT_FMT "\n", info.num_1block, info.num_2block, info.max_block);
132132
}
133133

134+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS || MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
134135
if (n_args == 1) {
135136
// arg given means dump gc allocation table
136-
gc_dump_alloc_table();
137+
gc_dump_alloc_table(true, true);
137138
}
139+
#endif
138140

139141
return mp_const_none;
140142
}

ports/renesas-ra/modmachine.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,12 @@ STATIC mp_obj_t machine_info(size_t n_args, const mp_obj_t *args) {
152152
pyb_thread_dump();
153153
#endif
154154

155+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS || MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
155156
if (n_args == 1) {
156157
// arg given means dump gc allocation table
157-
gc_dump_alloc_table();
158+
gc_dump_alloc_table(true, true);
158159
}
160+
#endif
159161

160162
return mp_const_none;
161163
}

ports/stm32/modmachine.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,12 @@ STATIC mp_obj_t machine_info(size_t n_args, const mp_obj_t *args) {
240240
pyb_thread_dump();
241241
#endif
242242

243+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS || MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
243244
if (n_args == 1) {
244245
// arg given means dump gc allocation table
245-
gc_dump_alloc_table();
246+
gc_dump_alloc_table(true, true);
246247
}
248+
#endif
247249

248250
return mp_const_none;
249251
}

ports/stm32/mpconfigport.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@
8080
#define MICROPY_SCHEDULER_DEPTH (8)
8181
#define MICROPY_VFS (1)
8282

83+
#if defined(STM32F0) || defined(STM32L0)
84+
// Only support basic GC stats on constrained devices.
85+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS (0)
86+
#endif
87+
8388
// control over Python builtins
8489
#ifndef MICROPY_PY_BUILTINS_HELP_TEXT
8590
#define MICROPY_PY_BUILTINS_HELP_TEXT stm32_help_text

ports/teensy/modpyb.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ STATIC mp_obj_t pyb_info(uint n_args, const mp_obj_t *args) {
111111

112112
if (n_args == 1) {
113113
// arg given means dump gc allocation table
114-
gc_dump_alloc_table();
114+
gc_dump_alloc_table(true, true);
115115
}
116116

117117
return mp_const_none;

ports/teensy/mpconfigport.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
1616
#define MICROPY_OPT_COMPUTED_GOTO (1)
1717

18+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS (1)
19+
1820
#define MICROPY_PY_BUILTINS_INPUT (1)
1921
#define MICROPY_PY_BUILTINS_HELP (1)
2022
#define MICROPY_PY_BUILTINS_HELP_TEXT teensy_help_text

ports/windows/mpconfigport.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@
125125
#define MICROPY_STACKLESS (0)
126126
#define MICROPY_STACKLESS_STRICT (0)
127127
#endif
128+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS (1)
129+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION (1)
128130

129131
#define MICROPY_PY_UOS (1)
130132
#define MICROPY_PY_UOS_INCLUDEFILE "ports/unix/moduos.c"

py/gc.c

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) {
569569
#endif
570570

571571
#if EXTENSIVE_HEAP_PROFILING
572-
gc_dump_alloc_table();
572+
gc_dump_alloc_table(true, true);
573573
#endif
574574

575575
return ret_ptr;
@@ -623,7 +623,7 @@ void gc_free(void *ptr) {
623623
GC_EXIT();
624624

625625
#if EXTENSIVE_HEAP_PROFILING
626-
gc_dump_alloc_table();
626+
gc_dump_alloc_table(true, true);
627627
#endif
628628
}
629629
}
@@ -752,7 +752,7 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
752752
GC_EXIT();
753753

754754
#if EXTENSIVE_HEAP_PROFILING
755-
gc_dump_alloc_table();
755+
gc_dump_alloc_table(true, true);
756756
#endif
757757

758758
return ptr_in;
@@ -777,7 +777,7 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
777777
#endif
778778

779779
#if EXTENSIVE_HEAP_PROFILING
780-
gc_dump_alloc_table();
780+
gc_dump_alloc_table(true, true);
781781
#endif
782782

783783
return ptr_in;
@@ -820,27 +820,59 @@ void gc_dump_info(void) {
820820
(uint)info.num_1block, (uint)info.num_2block, (uint)info.max_block, (uint)info.max_free);
821821
}
822822

823-
void gc_dump_alloc_table(void) {
823+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
824+
// Power-of-two size classes. i.e. class[0] is how many single-block
825+
// allocations are possible. class[5] is how many 32-block allocations.
826+
#define NUM_FRAGMENTATION_CLASSES (20)
827+
828+
// Given a number of contiguous blocks, figure out how many allocations of
829+
// each size class would fit in that many blocks.
830+
STATIC void gc_update_fragmentation_stats(size_t n, MICROPY_GC_STACK_ENTRY_TYPE *frag_classes) {
831+
for (size_t c = 1, i = 0; c < n && i < NUM_FRAGMENTATION_CLASSES; c <<= 1, ++i) {
832+
frag_classes[i] += n / c;
833+
}
834+
}
835+
#endif
836+
837+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS || MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
838+
void gc_dump_alloc_table(bool print_table, bool print_fragmentation) {
824839
GC_ENTER();
840+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
825841
static const size_t DUMP_BYTES_PER_LINE = 64;
842+
#endif
826843
#if !EXTENSIVE_HEAP_PROFILING
827-
// When comparing heap output we don't want to print the starting
828-
// pointer of the heap because it changes from run to run.
829-
mp_printf(&mp_plat_print, "GC memory layout; from %p:", MP_STATE_MEM(gc_pool_start));
844+
// Skip this when EXTENSIVE_HEAP_PROFILING is on because the starting
845+
// pointer of the heap changes from run to run.
846+
if (print_table) {
847+
mp_printf(&mp_plat_print, "GC memory layout; from %p:", MP_STATE_MEM(gc_pool_start));
848+
}
849+
#endif
850+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
851+
// How many consecutive free blocks.
852+
size_t nfree = 0;
853+
// Number of allocs that would succeed for 1, 2, 4, .. 2^n blocks.
854+
MICROPY_GC_STACK_ENTRY_TYPE frag_classes[NUM_FRAGMENTATION_CLASSES] = {0};
830855
#endif
831856
for (size_t bl = 0; bl < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; bl++) {
832-
if (bl % DUMP_BYTES_PER_LINE == 0) {
857+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
858+
if (print_table && bl % DUMP_BYTES_PER_LINE == 0) {
833859
// a new line of blocks
834860
{
835861
// check if this line contains only free blocks
836862
size_t bl2 = bl;
837863
while (bl2 < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB && ATB_GET_KIND(bl2) == AT_FREE) {
838864
bl2++;
839865
}
840-
if (bl2 - bl >= 2 * DUMP_BYTES_PER_LINE) {
866+
size_t skip = bl2 - bl;
867+
size_t lines = skip / DUMP_BYTES_PER_LINE;
868+
if (lines >= 2) {
841869
// there are at least 2 lines containing only free blocks, so abbreviate their printing
842-
mp_printf(&mp_plat_print, "\n (%u lines all free)", (uint)(bl2 - bl) / DUMP_BYTES_PER_LINE);
843-
bl = bl2 & (~(DUMP_BYTES_PER_LINE - 1));
870+
mp_printf(&mp_plat_print, "\n (%u lines all free)", (uint)lines);
871+
skip = lines * DUMP_BYTES_PER_LINE;
872+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
873+
nfree += skip;
874+
#endif
875+
bl += skip;
844876
if (bl >= MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB) {
845877
// got to end of heap
846878
break;
@@ -852,13 +884,22 @@ void gc_dump_alloc_table(void) {
852884
// mp_printf(&mp_plat_print, "\n%05x: ", (uint)(PTR_FROM_BLOCK(bl) & (uint32_t)0xfffff));
853885
mp_printf(&mp_plat_print, "\n%05x: ", (uint)((bl * BYTES_PER_BLOCK) & (uint32_t)0xfffff));
854886
}
887+
#endif
888+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
855889
int c = ' ';
890+
#endif
856891
switch (ATB_GET_KIND(bl)) {
857892
case AT_FREE:
893+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
858894
c = '.';
895+
#endif
896+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
897+
++nfree;
898+
#endif
859899
break;
860900
/* this prints out if the object is reachable from BSS or STACK (for unix only)
861901
case AT_HEAD: {
902+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
862903
c = 'h';
863904
void **ptrs = (void**)(void*)&mp_state_ctx;
864905
mp_uint_t len = offsetof(mp_state_ctx_t, vm.stack_top) / sizeof(mp_uint_t);
@@ -880,11 +921,13 @@ void gc_dump_alloc_table(void) {
880921
}
881922
}
882923
}
883-
break;
924+
#endif
925+
goto reset_frag;
884926
}
885927
*/
886928
/* this prints the uPy object type of the head block */
887929
case AT_HEAD: {
930+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
888931
void **ptr = (void **)(MP_STATE_MEM(gc_pool_start) + bl * BYTES_PER_BLOCK);
889932
if (*ptr == &mp_type_tuple) {
890933
c = 'T';
@@ -934,20 +977,47 @@ void gc_dump_alloc_table(void) {
934977
}
935978
#endif
936979
}
937-
break;
980+
#endif
981+
goto reset_frag;
938982
}
939983
case AT_TAIL:
984+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
940985
c = '=';
941-
break;
986+
#endif
987+
goto reset_frag;
942988
case AT_MARK:
989+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
943990
c = 'm';
991+
#endif
992+
goto reset_frag;
993+
reset_frag:
994+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
995+
gc_update_fragmentation_stats(nfree, frag_classes);
996+
nfree = 0;
997+
#endif
944998
break;
945999
}
946-
mp_printf(&mp_plat_print, "%c", c);
1000+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
1001+
if (print_table) {
1002+
mp_printf(&mp_plat_print, "%c", c);
1003+
}
1004+
#endif
1005+
}
1006+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
1007+
if (print_fragmentation) {
1008+
gc_update_fragmentation_stats(nfree, frag_classes);
1009+
mp_print_str(&mp_plat_print, "Frag:");
1010+
size_t c = 1;
1011+
for (int i = 0; i < NUM_FRAGMENTATION_CLASSES; ++i) {
1012+
mp_printf(&mp_plat_print, " %u: %u,", c, frag_classes[i]);
1013+
c <<= 1;
1014+
}
9471015
}
1016+
#endif
9481017
mp_print_str(&mp_plat_print, "\n");
9491018
GC_EXIT();
9501019
}
1020+
#endif
9511021

9521022
#if 0
9531023
// For testing the GC functions
@@ -976,13 +1046,13 @@ void gc_test(void) {
9761046
}
9771047

9781048
printf("Before GC:\n");
979-
gc_dump_alloc_table();
1049+
gc_dump_alloc_table(true, true);
9801050
printf("Starting GC...\n");
9811051
gc_collect_start();
9821052
gc_collect_root(ptrs, sizeof(ptrs) / sizeof(void *));
9831053
gc_collect_end();
9841054
printf("After GC:\n");
985-
gc_dump_alloc_table();
1055+
gc_dump_alloc_table(true, true);
9861056
}
9871057
#endif
9881058

py/gc.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ typedef struct _gc_info_t {
6767

6868
void gc_info(gc_info_t *info);
6969
void gc_dump_info(void);
70-
void gc_dump_alloc_table(void);
70+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS || MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
71+
void gc_dump_alloc_table(bool print_table, bool print_fragmentation);
72+
#endif
7173

7274
#endif // MICROPY_INCLUDED_PY_GC_H

py/modmicropython.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_mem_peak_obj, mp_micropython_mem
6767
#endif
6868

6969
mp_obj_t mp_micropython_mem_info(size_t n_args, const mp_obj_t *args) {
70-
(void)args;
7170
#if MICROPY_MEM_STATS
7271
mp_printf(&mp_plat_print, "mem: total=" UINT_FMT ", current=" UINT_FMT ", peak=" UINT_FMT "\n",
7372
(mp_uint_t)m_get_total_bytes_allocated(), (mp_uint_t)m_get_current_bytes_allocated(), (mp_uint_t)m_get_peak_bytes_allocated());
@@ -80,13 +79,17 @@ mp_obj_t mp_micropython_mem_info(size_t n_args, const mp_obj_t *args) {
8079
#endif
8180
#if MICROPY_ENABLE_GC
8281
gc_dump_info();
82+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS || MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
8383
if (n_args == 1) {
84-
// arg given means dump gc allocation table
85-
gc_dump_alloc_table();
84+
// 1 = block table, 2 = frag stats, 3 = both.
85+
mp_int_t arg = mp_obj_get_int(args[0]);
86+
gc_dump_alloc_table(arg & 1, arg & 2);
8687
}
8788
#else
8889
(void)n_args;
89-
#endif
90+
(void)args;
91+
#endif // MICROPY_PY_MICROPYTHON_MEM_INFO_*
92+
#endif // MICROPY_ENABLE_GC
9093
return mp_const_none;
9194
}
9295
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_micropython_mem_info_obj, 0, 1, mp_micropython_mem_info);

py/mpconfig.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1184,7 +1184,17 @@ typedef double mp_float_t;
11841184

11851185
// Whether to provide mem-info related functions in micropython module
11861186
#ifndef MICROPY_PY_MICROPYTHON_MEM_INFO
1187-
#define MICROPY_PY_MICROPYTHON_MEM_INFO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
1187+
#define MICROPY_PY_MICROPYTHON_MEM_INFO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES)
1188+
#endif
1189+
1190+
// If mem-info is available, whether to support the full block dump.
1191+
#ifndef MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
1192+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
1193+
#endif
1194+
1195+
// If mem-info is available, whether to support fragmentation stats.
1196+
#ifndef MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
1197+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES)
11881198
#endif
11891199

11901200
// Whether to provide "micropython.stack_use" function

0 commit comments

Comments
 (0)
0