Skip to content

Add CIRCUITPY_HEAP_SRAM_SIZE: opt-in internal-RAM heap start segment - #11176

Open
lynt-smitka wants to merge 1 commit into
adafruit:mainfrom
MakerClassCZ:heap-sram-start-size
Open

Add CIRCUITPY_HEAP_SRAM_SIZE: opt-in internal-RAM heap start segment#11176
lynt-smitka wants to merge 1 commit into
adafruit:mainfrom
MakerClassCZ:heap-sram-start-size

Conversation

@lynt-smitka

Copy link
Copy Markdown

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:

measurement (Fruit Jam) PSRAM heap internal-RAM heap
span writes into a heap bytearray ~8.7 MB/s 64+ MB/s
38 KB RGB565 buffer clear/fill 4.2 ms 0.23 ms (18×)
full-frame game loop baseline ~10 % faster overall

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 via MP_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).

  • Key unset (default): behavior is completely unchanged.
  • Oversized request: the internal allocation fails and the heap starts in PSRAM as before.
  • Interaction with the existing CIRCUITPY_HEAP_START_SIZE (which sizes the initial PSRAM segment): when the new key is satisfied it fully defines the start segment and CIRCUITPY_HEAP_START_SIZE is not consulted; if the new key is unset or its internal-RAM allocation fails, CIRCUITPY_HEAP_START_SIZE applies 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.

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 tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants