Add CIRCUITPY_HEAP_SRAM_SIZE: opt-in internal-RAM heap start segment - #11176
Open
lynt-smitka wants to merge 1 commit into
Open
Add CIRCUITPY_HEAP_SRAM_SIZE: opt-in internal-RAM heap start segment#11176lynt-smitka wants to merge 1 commit into
lynt-smitka wants to merge 1 commit into
Conversation
On boards whose python heap defaults to external PSRAM (adafruit#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 adafruit#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.
tannewt
reviewed
Aug 4, 2026
tannewt
left a comment
Member
There was a problem hiding this comment.
I'm not sure about this. I don't really want another settings.toml knob, especially one like this where a very specific value would be needed for a specific case.
Instead, I'd prefer a general speed up based on analysis of what memory reads are actually being done that are too slow on PSRAM. Maybe the heap metadata needs to be in internal ram? Do you have a more detailed analysis of what memory accesses are slow?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Since #10240 the VM heap on PSRAM boards lives entirely in external PSRAM. In that PR's review @dhalbert noted: "it will reduce performance on boards with PSRAM -- I wonder by how much." I measured it while working on a game engine, and the cost is significant enough to deserve an opt-out for performance-sensitive workloads:
The last row is the interesting one: it's not just big buffers - the interpreter's working set (module bytecode, young objects) pays the PSRAM latency too.
What this does
Adds one opt-in settings.toml key:
CIRCUITPY_HEAP_SRAM_SIZE = <bytes>allocates only the heap's start segment from the internal (dma-capable) pool. Growth segments still come from PSRAM viaMP_PLAT_ALLOC_HEAP, so allocations made early in a program run at internal-RAM speed while total capacity is unchanged (everything else spills over transparently).CIRCUITPY_HEAP_START_SIZE(which sizes the initial PSRAM segment): when the new key is satisfied it fully defines the start segment andCIRCUITPY_HEAP_START_SIZEis not consulted; if the new key is unset or its internal-RAM allocation fails,CIRCUITPY_HEAP_START_SIZEapplies as usual.Tradeoff (documented in
docs/environment.rst)The internal pool is shared with DMA buffers (displays, audio). Allocations made at boot coexist fine; in the rare case of re-allocating a large DMA buffer at runtime (e.g. switching a framebuffer to a higher resolution), the request may no longer fit and raises a regular catchable MemoryError.