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

Skip to content

Commit 6f38204

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.
1 parent e3c880a commit 6f38204

File tree

9 files changed

+129
-30
lines changed

9 files changed

+129
-30
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ STATIC mp_obj_t machine_info(mp_uint_t n_args, const mp_obj_t *args) {
131131

132132
if (n_args == 1) {
133133
// arg given means dump gc allocation table
134-
gc_dump_alloc_table();
134+
gc_dump_alloc_table(true, true);
135135
}
136136

137137
return mp_const_none;

ports/stm32/modmachine.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ STATIC mp_obj_t machine_info(size_t n_args, const mp_obj_t *args) {
242242

243243
if (n_args == 1) {
244244
// arg given means dump gc allocation table
245-
gc_dump_alloc_table();
245+
gc_dump_alloc_table(true, true);
246246
}
247247

248248
return mp_const_none;

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;

py/gc.c

Lines changed: 86 additions & 16 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, size_t *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
9471005
}
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+
}
1015+
}
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

py/gc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ 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+
void gc_dump_alloc_table(bool print_table, bool print_fragmentation);
7171

7272
#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
@@ -1194,7 +1194,17 @@ typedef double mp_float_t;
11941194

11951195
// Whether to provide mem-info related functions in micropython module
11961196
#ifndef MICROPY_PY_MICROPYTHON_MEM_INFO
1197-
#define MICROPY_PY_MICROPYTHON_MEM_INFO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
1197+
#define MICROPY_PY_MICROPYTHON_MEM_INFO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES)
1198+
#endif
1199+
1200+
// If mem-info is available, whether to support the full block dump.
1201+
#ifndef MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
1202+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
1203+
#endif
1204+
1205+
// If mem-info is available, whether to support fragmentation stats.
1206+
#ifndef MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
1207+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_BASIC_FEATURES)
11981208
#endif
11991209

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

0 commit comments

Comments
 (0)
0