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

Skip to content

Commit b4a36e3

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. Now that argument can either be ``1`` or ``2``. ``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. On small STM32 ports (L0, F0), don't enable support for the full block table, only for the frag stats.
1 parent 388d419 commit b4a36e3

File tree

10 files changed

+122
-29
lines changed

10 files changed

+122
-29
lines changed

docs/library/micropython.rst

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,18 @@ 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.
63-
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.
61+
Print information about currently used memory. The information that is
62+
printed is implementation dependent, but currently includes the amount of
63+
stack and heap used. If the *verbose* argument is given then extra
64+
information is printed.
65+
66+
If *verbose* equals ``1``, then it will additional print out the entire
67+
heap, showing the status of each block. (This may not be supported on all
68+
boards and ports).
69+
70+
If *verbose* equals ``2``, then it will additionally print a summary of
71+
heap fragmentation, showing the number of allocations that would succeed
72+
for successive powers of two sized allocations.
6773

6874
.. function:: qstr_info([verbose])
6975

ports/esp8266/modpyb.c

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

7272
if (n_args == 1) {
7373
// arg given means dump gc allocation table
74-
gc_dump_alloc_table();
74+
gc_dump_alloc_table(true, true);
7575
}
7676

7777
return mp_const_none;

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
@@ -226,7 +226,7 @@ STATIC mp_obj_t machine_info(size_t n_args, const mp_obj_t *args) {
226226

227227
if (n_args == 1) {
228228
// arg given means dump gc allocation table
229-
gc_dump_alloc_table();
229+
gc_dump_alloc_table(true, true);
230230
}
231231

232232
return mp_const_none;

ports/stm32/mpconfigport.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@
8686
#define MICROPY_SCHEDULER_DEPTH (8)
8787
#define MICROPY_VFS (1)
8888

89+
#if defined(STM32F0) || defined(STM32L0)
90+
// Only support basic GC stats on constrained devices.
91+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS (0)
92+
#endif
93+
8994
// control over Python builtins
9095
#define MICROPY_PY_FUNCTION_ATTRS (1)
9196
#define MICROPY_PY_DESCRIPTORS (1)

ports/teensy/modpyb.c

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

116116
if (n_args == 1) {
117117
// arg given means dump gc allocation table
118-
gc_dump_alloc_table();
118+
gc_dump_alloc_table(true, true);
119119
}
120120

121121
return mp_const_none;

py/gc.c

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) {
553553
#endif
554554

555555
#if EXTENSIVE_HEAP_PROFILING
556-
gc_dump_alloc_table();
556+
gc_dump_alloc_table(true, true);
557557
#endif
558558

559559
return ret_ptr;
@@ -607,7 +607,7 @@ void gc_free(void *ptr) {
607607
GC_EXIT();
608608

609609
#if EXTENSIVE_HEAP_PROFILING
610-
gc_dump_alloc_table();
610+
gc_dump_alloc_table(true, true);
611611
#endif
612612
}
613613
}
@@ -737,7 +737,7 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
737737
GC_EXIT();
738738

739739
#if EXTENSIVE_HEAP_PROFILING
740-
gc_dump_alloc_table();
740+
gc_dump_alloc_table(true, true);
741741
#endif
742742

743743
return ptr_in;
@@ -762,7 +762,7 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
762762
#endif
763763

764764
#if EXTENSIVE_HEAP_PROFILING
765-
gc_dump_alloc_table();
765+
gc_dump_alloc_table(true, true);
766766
#endif
767767

768768
return ptr_in;
@@ -805,27 +805,59 @@ void gc_dump_info(void) {
805805
(uint)info.num_1block, (uint)info.num_2block, (uint)info.max_block, (uint)info.max_free);
806806
}
807807

808-
void gc_dump_alloc_table(void) {
808+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
809+
// Power-of-two size classes. i.e. class[0] is how many single-block
810+
// allocations are possible. class[5] is how many 32-block allocations.
811+
#define NUM_FRAGMENTATION_CLASSES (20)
812+
813+
// Given a number of contiguous blocks, figure out how many allocations of
814+
// each size class would fit in that many blocks.
815+
STATIC void gc_update_fragmentation_stats(size_t n, size_t *frag_classes) {
816+
for (size_t c = 1, i = 0; c < n && i < NUM_FRAGMENTATION_CLASSES; c <<= 1, ++i) {
817+
frag_classes[i] += n / c;
818+
}
819+
}
820+
#endif
821+
822+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS || MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
823+
void gc_dump_alloc_table(bool print_table, bool print_fragmentation) {
809824
GC_ENTER();
825+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
810826
static const size_t DUMP_BYTES_PER_LINE = 64;
827+
#endif
811828
#if !EXTENSIVE_HEAP_PROFILING
812829
// When comparing heap output we don't want to print the starting
813830
// pointer of the heap because it changes from run to run.
814-
mp_printf(&mp_plat_print, "GC memory layout; from %p:", MP_STATE_MEM(gc_pool_start));
831+
if (print_table) {
832+
mp_printf(&mp_plat_print, "GC memory layout; from %p:", MP_STATE_MEM(gc_pool_start));
833+
}
834+
#endif
835+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
836+
// How many consecutive free blocks.
837+
size_t nfree = 0;
838+
// Number of allocs that would succeed for 1, 2, 4, .. 2^n blocks.
839+
size_t frag_classes[NUM_FRAGMENTATION_CLASSES] = {0};
815840
#endif
816841
for (size_t bl = 0; bl < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; bl++) {
817-
if (bl % DUMP_BYTES_PER_LINE == 0) {
842+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
843+
if (print_table && bl % DUMP_BYTES_PER_LINE == 0) {
818844
// a new line of blocks
819845
{
820846
// check if this line contains only free blocks
821847
size_t bl2 = bl;
822848
while (bl2 < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB && ATB_GET_KIND(bl2) == AT_FREE) {
823849
bl2++;
824850
}
825-
if (bl2 - bl >= 2 * DUMP_BYTES_PER_LINE) {
851+
size_t skip = bl2 - bl;
852+
size_t lines = skip / DUMP_BYTES_PER_LINE;
853+
if (lines >= 2) {
826854
// there are at least 2 lines containing only free blocks, so abbreviate their printing
827-
mp_printf(&mp_plat_print, "\n (%u lines all free)", (uint)(bl2 - bl) / DUMP_BYTES_PER_LINE);
828-
bl = bl2 & (~(DUMP_BYTES_PER_LINE - 1));
855+
mp_printf(&mp_plat_print, "\n (%u lines all free)", (uint)lines);
856+
skip = lines * DUMP_BYTES_PER_LINE;
857+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
858+
nfree += skip;
859+
#endif
860+
bl += skip;
829861
if (bl >= MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB) {
830862
// got to end of heap
831863
break;
@@ -837,13 +869,22 @@ void gc_dump_alloc_table(void) {
837869
//mp_printf(&mp_plat_print, "\n%05x: ", (uint)(PTR_FROM_BLOCK(bl) & (uint32_t)0xfffff));
838870
mp_printf(&mp_plat_print, "\n%05x: ", (uint)((bl * BYTES_PER_BLOCK) & (uint32_t)0xfffff));
839871
}
872+
#endif
873+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
840874
int c = ' ';
875+
#endif
841876
switch (ATB_GET_KIND(bl)) {
842877
case AT_FREE:
878+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
843879
c = '.';
880+
#endif
881+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
882+
++nfree;
883+
#endif
844884
break;
845885
/* this prints out if the object is reachable from BSS or STACK (for unix only)
846886
case AT_HEAD: {
887+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
847888
c = 'h';
848889
void **ptrs = (void**)(void*)&mp_state_ctx;
849890
mp_uint_t len = offsetof(mp_state_ctx_t, vm.stack_top) / sizeof(mp_uint_t);
@@ -865,11 +906,13 @@ void gc_dump_alloc_table(void) {
865906
}
866907
}
867908
}
868-
break;
909+
#endif
910+
goto reset_frag;
869911
}
870912
*/
871913
/* this prints the uPy object type of the head block */
872914
case AT_HEAD: {
915+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
873916
void **ptr = (void **)(MP_STATE_MEM(gc_pool_start) + bl * BYTES_PER_BLOCK);
874917
if (*ptr == &mp_type_tuple) {
875918
c = 'T';
@@ -919,20 +962,47 @@ void gc_dump_alloc_table(void) {
919962
}
920963
#endif
921964
}
922-
break;
965+
#endif
966+
goto reset_frag;
923967
}
924968
case AT_TAIL:
969+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
925970
c = '=';
926-
break;
971+
#endif
972+
goto reset_frag;
927973
case AT_MARK:
974+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
928975
c = 'm';
976+
#endif
977+
goto reset_frag;
978+
reset_frag:
979+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
980+
gc_update_fragmentation_stats(nfree, frag_classes);
981+
nfree = 0;
982+
#endif
929983
break;
930984
}
931-
mp_printf(&mp_plat_print, "%c", c);
985+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
986+
if (print_table) {
987+
mp_printf(&mp_plat_print, "%c", c);
988+
}
989+
#endif
932990
}
991+
#if MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
992+
if (print_fragmentation) {
993+
gc_update_fragmentation_stats(nfree, frag_classes);
994+
mp_print_str(&mp_plat_print, "Frag:");
995+
size_t c = 1;
996+
for (int i = 0; i < NUM_FRAGMENTATION_CLASSES; ++i) {
997+
mp_printf(&mp_plat_print, " %u: %u,", c, frag_classes[i]);
998+
c <<= 1;
999+
}
1000+
}
1001+
#endif
9331002
mp_print_str(&mp_plat_print, "\n");
9341003
GC_EXIT();
9351004
}
1005+
#endif
9361006

9371007
#if 0
9381008
// For testing the GC functions

py/gc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,6 @@ typedef struct _gc_info_t {
6969

7070
void gc_info(gc_info_t *info);
7171
void gc_dump_info(void);
72-
void gc_dump_alloc_table(void);
72+
void gc_dump_alloc_table(bool print_table, bool print_fragmentation);
7373

7474
#endif // MICROPY_INCLUDED_PY_GC_H

py/modmicropython.c

Lines changed: 6 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,16 @@ 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+
mp_int_t arg = mp_obj_get_int(args[0]);
85+
gc_dump_alloc_table(arg == 1, arg == 2);
8686
}
8787
#else
8888
(void)n_args;
89-
#endif
89+
(void)args;
90+
#endif // MICROPY_PY_MICROPYTHON_MEM_INFO_*
91+
#endif // MICROPY_ENABLE_GC
9092
return mp_const_none;
9193
}
9294
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_micropython_mem_info_obj, 0, 1, mp_micropython_mem_info);

py/mpconfig.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,16 @@ typedef double mp_float_t;
10591059
#define MICROPY_PY_MICROPYTHON_MEM_INFO (0)
10601060
#endif
10611061

1062+
// If mem-info is available, whether to support the full block dump.
1063+
#ifndef MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS
1064+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_BLOCKS (1)
1065+
#endif
1066+
1067+
// If mem-info is available, whether to support fragmentation stats.
1068+
#ifndef MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION
1069+
#define MICROPY_PY_MICROPYTHON_MEM_INFO_FRAGMENTATION (1)
1070+
#endif
1071+
10621072
// Whether to provide "micropython.stack_use" function
10631073
#ifndef MICROPY_PY_MICROPYTHON_STACK_USE
10641074
#define MICROPY_PY_MICROPYTHON_STACK_USE (MICROPY_PY_MICROPYTHON_MEM_INFO)

0 commit comments

Comments
 (0)
0