From ebb7b0d4e310f37eac41bf652d2eaee8666f03d1 Mon Sep 17 00:00:00 2001 From: Vladimir Smitka Date: Tue, 4 Aug 2026 09:19:19 +0000 Subject: [PATCH] Add CIRCUITPY_HEAP_SRAM_SIZE: opt-in internal-RAM heap start segment On boards whose python heap defaults to external PSRAM (#10240), all python allocations pay external-memory latency. Measured on an Adafruit Fruit Jam (RP2350 + 8 MB PSRAM): span writes reach ~8.7 MB/s vs ~64+ MB/s in internal SRAM; clearing/filling a 38 KB RGB565 buffer takes 4.2 ms from PSRAM vs 0.23 ms from SRAM (18x), and a bytecode-heavy program's frame time improved ~10% overall with its early allocations in SRAM - the interpreter working set (module bytecode, young objects) pays the PSRAM tax too. This answers the open review question on #10240 about how much the PSRAM heap costs. Since the heap is already segmented (start segment + auto-grown splits), a minimal opt-in helps a lot: the new settings.toml key allocates just the START segment from the internal (dma-capable) pool, while growth segments keep coming from PSRAM - allocations made early in a program run at internal-RAM speed and everything else spills over transparently. Unset (default) keeps today's behavior. An oversized request simply fails the internal allocation and falls back to the PSRAM path. The documented tradeoff: the internal pool is shared with display/audio DMA buffers, so a large segment can prevent later large DMA allocations on that board - an explicit per-device choice. --- docs/environment.rst | 18 ++++++++++++++++++ main.c | 20 +++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/environment.rst b/docs/environment.rst index 4bf89bf774a..dcdf7fe886f 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -93,6 +93,24 @@ Larger values will reserve more RAM for python use and prevent the supervisor an from large allocations of their own. Smaller values will likely grow sooner than large start sizes. +CIRCUITPY_HEAP_SRAM_SIZE (integer) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +On boards where the python heap is normally placed in external PSRAM, allocate the heap's +*initial* segment (of this many bytes) from internal RAM instead. Later heap growth still +comes from PSRAM, so allocations made early in a program run at internal-RAM speed while +the total capacity is unchanged. If the requested size does not fit, the setting is +ignored and the heap starts in PSRAM as usual. Unset by default. + +When this setting is satisfied it fully defines the initial segment and +``CIRCUITPY_HEAP_START_SIZE`` is not consulted; ``CIRCUITPY_HEAP_START_SIZE`` applies +only when this setting is unset or its internal-RAM allocation fails. + +The internal pool is shared with DMA buffers (displays, audio). Buffers allocated at boot +coexist with this setting, but *re-allocating* a large buffer later (for example switching +a framebuffer display to a higher resolution at runtime) may fail, because the freed space +cannot merge across the live heap segment. With this setting, choose such sizes at boot +(e.g. via ``CIRCUITPY_DISPLAY_WIDTH``/``HEIGHT``) rather than switching at runtime. + CIRCUITPY_PYSTACK_SIZE (integer) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Sets the size of the python stack. Must be a multiple of 4. The default value is currently 1536. diff --git a/main.c b/main.c index 2020cb8c307..f3c8148ac65 100644 --- a/main.c +++ b/main.c @@ -197,7 +197,25 @@ static void start_mp(safe_mode_t safe_mode) { #if MICROPY_ENABLE_GC size_t heap_size = 0; - _heap = _allocate_memory(safe_mode, "CIRCUITPY_HEAP_START_SIZE", CIRCUITPY_HEAP_START_SIZE, &heap_size); + // If CIRCUITPY_HEAP_SRAM_SIZE is set, try to place the heap's START segment in the + // internal (dma-capable) pool. On boards whose default heap lives in external PSRAM + // this puts early/hot allocations in fast RAM, while the heap still grows into PSRAM + // via MP_PLAT_ALLOC_HEAP. Unset (the default) keeps the current behavior; if the + // requested size does not fit the internal pool, we fall back to it as well. + #if CIRCUITPY_SETTINGS_TOML + if (safe_mode == SAFE_MODE_NONE) { + mp_int_t sram_size; + if (settings_get_int("CIRCUITPY_HEAP_SRAM_SIZE", &sram_size) == SETTINGS_OK && sram_size > 0) { + _heap = port_malloc((size_t)sram_size, true); + if (_heap != NULL) { + heap_size = (size_t)sram_size; + } + } + } + #endif + if (_heap == NULL) { + _heap = _allocate_memory(safe_mode, "CIRCUITPY_HEAP_START_SIZE", CIRCUITPY_HEAP_START_SIZE, &heap_size); + } gc_init(_heap, _heap + heap_size); #endif mp_init();