Skip to content

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

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