Skip to content

Commit 18dbdf9

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 18dbdf9

File tree

10 files changed

+124
-31
lines changed

10 files changed

+124
-31
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: 86 additions & 16 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
812-
// When comparing heap output we don't want to print the starting
813-
// 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));
829+
// Skip this when EXTENSIVE_HEAP_PROFILING is on because the starting
830+
// pointer of the heap changes from run to run.
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy