Use mimalloc as the Python allocator instead of preloading jemalloc - #175604
Conversation
Select mimalloc via PYTHONMALLOC=mimalloc instead of LD_PRELOAD-ing jemalloc. mimalloc is bundled in CPython 3.13+, so no extra library or LD_PRELOAD is needed, and it uses noticeably less peak RSS than jemalloc with no measurable CPU regression on the pyperformance suite. PYTHONMALLOC stays user-overridable (e.g. PYTHONMALLOC=pymalloc for the default allocator). Warn when the legacy DISABLE_JEMALLOC variable is set, since it no longer has any effect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR changes how the Home Assistant Core container selects its memory allocator. Instead of preloading jemalloc process-wide via LD_PRELOAD (with a MALLOC_CONF tuning string and a DISABLE_JEMALLOC opt-out), it now selects CPython's bundled mimalloc via PYTHONMALLOC=mimalloc. The motivation is to avoid jemalloc's build-time page-size assumption (which aborts on 16 KB/64 KB-page aarch64 systems and is the reason DISABLE_JEMALLOC existed) and to reduce peak RSS, while keeping the allocator user-overridable through PYTHONMALLOC. The now-inert DISABLE_JEMALLOC variable emits a warning pointing users to PYTHONMALLOC=pymalloc.
Changes:
- Replace the jemalloc
LD_PRELOAD/MALLOC_CONFblock withexport PYTHONMALLOC="${PYTHONMALLOC:-mimalloc}", keeping it user-overridable. - Add a startup warning when the deprecated
DISABLE_JEMALLOCvariable is still set, directing users to thePYTHONMALLOCopt-out.
| # Use mimalloc as Python's object allocator by default. It is bundled in CPython | ||
| # (3.13+), so no LD_PRELOAD or extra library is required, and it uses noticeably | ||
| # less memory than the previously preloaded jemalloc. Override or disable via | ||
| # PYTHONMALLOC, e.g. `PYTHONMALLOC=pymalloc` for the default allocator. |
|
Full table of benchmarks I've run to evaluate the situation with Python 3.14
What the columns mean
How the configs were set
Environment & caveats
|
PR Review — Use mimalloc as the Python allocator instead of preloading jemallocSound, well-motivated allocator change; one robustness gap (no fallback if a build lacks mimalloc) is worth closing before merge. Strengths worth calling out:
What needs attention:
🟡 Important
1. PYTHONMALLOC=mimalloc is a hard, fatal dependency on a mimalloc-enabled build with no runtime fallback
|
bluetoothbot
left a comment
There was a problem hiding this comment.
Blocking issues found.
- PYTHONMALLOC=mimalloc is a hard, fatal dependency on a mimalloc-enabled build with no runtime fallback
We maintain our base images, so this doesn't seem a blocker to me. |
|
Just need to test that we don't break developer venv or dev containers |
|
I've run the tests in independently again, and I can reproduce the CPU/memory benefits. From my perspective the main advantage using mimalloc over jemalloc is it's backing by upstream Python: It is the default for free-threaded builds, and should therefor be well supported. The fact that it support multiple page sizes at runtime makes it also the better option when it comes to architecture compatibility. |
|
Also note that the peak RSS size is probably mostly an artifact of the jemalloc tunning via |
|
@bdraco do you have any concerns still with this change? |
Breaking change
This is not a breaking change for configuration. One behavior note for transparency: the
DISABLE_JEMALLOCenvironment variable no longer has any effect (jemalloc is no longer preloaded) and now logs a warning. Users who set it — most commonly on aarch64 systems with 16 KB/64 KB memory pages, where preloaded jemalloc aborted withUnsupported system page size— no longer need it, because mimalloc does not have that incompatibility. To fall back to CPython's default allocator, setPYTHONMALLOC=defaultorPYTHONMALLOC=pymallocexplicitly.Proposed change
Switch the Home Assistant Core container from preloading jemalloc process-wide to selecting mimalloc as CPython's object allocator via
PYTHONMALLOC=mimalloc.In
rootfs/etc/services.d/home-assistant/run:PYTHONMALLOC=mimallocuses the mimalloc that is already bundled in CPython 3.13+ (--with-mimalloc, enabled by default), so noLD_PRELOADand no external allocator library are needed.PYTHONMALLOCstays fully user-overridable, so it doubles as the opt-out (PYTHONMALLOC=pymalloc) and replaces the single-purposeDISABLE_JEMALLOCflag.Motivation
jemalloc was originally adopted to work around the slow musl allocator, as documented in the 2020 developer blog post: https://developers.home-assistant.io/blog/2020/07/13/alpine-python/. That was measured against musl's old allocator; musl has since shipped
mallocng(default since musl 1.2.1), and the picture has changed.jemalloc no longer earns its keep here, and it carries a real portability hazard:
Page-size incompatibility on aarch64. jemalloc bakes the page size in at build time. A build assuming 4 KB pages aborts with
<jemalloc>: Unsupported system page sizeon kernels using 16 KB (Raspberry Pi 5, Apple Silicon) or 64 KB (Ampere, some RHEL/Fedora arm64) pages. This is exactly why theDISABLE_JEMALLOCescape hatch exists, and the same class of crash is widely reported across projects (Polars, Qdrant, Typesense, KeyDB, Reth, and others). mimalloc queries the page size at runtime (sysconf(_SC_PAGESIZE)in_mi_prim_mem_init()), so it adapts to 4/16/64 KB automatically — no compiled-in assumption, and no such crash reports exist against it. Switching to mimalloc removes the reasonDISABLE_JEMALLOCwas needed.Memory. Benchmarking the current image (Python 3.14, musl, GCC/full-LTO/PGO) shows jemalloc retains substantially more memory than either plain musl or mimalloc, for no measurable CPU benefit.
Benchmark results
CPU measured with the
pyperformancesuite (122 benchmarks — the same tool used in the 2020 blog); peak RSS measured with Core's built-in benchmark workload. All on the qemux86-64 image, CPU-pinned, warmed up.PYTHONMALLOC=mimalloc(this PR)LD_PRELOAD)Two takeaways: jemalloc shows no measurable CPU advantage over plain musl while using ~40% more peak RSS, and
PYTHONMALLOC=mimallocgives the best combined result — mimalloc's full memory benefit (~27% lower peak RSS than jemalloc) at CPU parity-or-better, withoutLD_PRELOADor an external library. Process-wide mimalloc viaLD_PRELOADwas tested too and was notably worse on memory, so this PR deliberately uses only thePYTHONMALLOCpath.Supporting references
PYTHONMALLOC=mimalloc(https://docs.python.org/3/c-api/memory.html); it is the default allocator for free-threaded (no-GIL) builds (Add mimalloc memory allocator python/cpython#90815), so this also aligns Core with CPython's direction.--with-lg-page=16the default option on ARM64 architecture jemalloc/jemalloc#2639.Caveats / where reviewer input is welcome
pyperformancenor the internal benchmarks fully exercise. Core runs a thread-pool executor and many integration threads, so real-world concurrent allocation may differ. This is the main thing worth validating — e.g. on the beta channel, or with a real startup + steady-state RSS profile.MALLOC_CONFretention tuning (dirty/muzzy_decay_ms) is dropped; mimalloc's defaults are used. If needed, mimalloc'sMIMALLOC_PURGE_DELAYis the analog (higher value = hold freed memory longer, trading RSS for fewer purge syscalls).Type of change
Additional information
Possible follow-ups, kept out of this PR to stay focused: drop the now-unused jemalloc apk package from the image (size reduction), and add a build/CI assertion that
PYTHONMALLOC=mimalloc python3 -c ''succeeds so a future base image cannot silently regress to musl.Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all.To help with the load of incoming pull requests: