Collect the settings that bring EOS from about 290 MB idle / 400-450 MB during a GENETIC run down to about 140 MB, with the measurements behind them: fitness cache, timezone override and SciPy-free self-consumption tables (#1376, #1377, #1378), autosave for SD/eMMC storage, and the glibc allocator and BLAS environment. Includes run times on a Raspberry Pi 4 and an x86 VM with data of a real installation. Co-authored-by: Christin <christin@BigBrother.bikini.bottom.zone> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
5.1 KiB
% SPDX-License-Identifier: Apache-2.0 (smalldevices-page)=
Running EOS on Small Devices
EOS runs on single-board computers with 512 MB to 1 GB of RAM and on eMMC or SD storage. With the default configuration it needs about 290 MB at idle and 400–450 MB while a GENETIC run is in progress. The settings on this page bring that down to about 140 MB, without changing the optimization result.
All figures below were measured with GENETIC at 300 individuals × 400 generations and 15-minute slots; the individual measurements state their conditions.
Summary
| Setting | Effect | Plan |
|---|---|---|
optimization.genetic.fitness_cache_max_entries: 0 1 |
−110 to −160 MB peak per run | identical |
general.timezone_override 2 |
−28 MB at startup (no timezone lookup) | identical |
optimization.self_consumption_interpolator: numpy 3 |
−37 MB after the first run (no SciPy) | identical |
database.autosave_interval_sec: null |
storage writes from ~13 MB/min to ~1 KiB/min | identical, see notes |
PYTHONMALLOC=malloc (environment) |
memory is returned after each run (glibc) | identical |
OPENBLAS_NUM_THREADS=1 (environment) |
no per-core BLAS buffers | identical |
Example configuration:
{
"general": { "timezone_override": "Europe/Berlin" },
"optimization": {
"self_consumption_interpolator": "numpy",
"genetic": { "fitness_cache_max_entries": 0 }
},
"database": { "autosave_interval_sec": null }
}
Environment of the EOS process (for example in the systemd unit):
Environment=PYTHONMALLOC=malloc
Environment=MALLOC_ARENA_MAX=2
Environment=MALLOC_TRIM_THRESHOLD_=131072
Environment=MALLOC_MMAP_THRESHOLD_=131072
Environment=OPENBLAS_NUM_THREADS=1
Measurements
Memory, all settings combined
Linux arm64 (Debian 13, glibc, Python 3.13), one CPU per container, server-like process (EOSdash off), 48 h horizon, 77 kWh battery with direct marketing; "with EV" adds an electric vehicle and a dynamic tariff.
| Idle | Peak during a run | Peak with EV | |
|---|---|---|---|
main (default settings) |
282–288 MB | 401 MB | 451 MB |
| all settings above | 136 MB | 142 MB | 144 MB |
With the fitness cache disabled, the memory no longer depends on the number of planned devices: with an EV and five home appliances the peak was 144 MB (about 300 MB with the unbounded cache). More devices only cost run time.
Fitness cache
Same platform, two consecutive runs, MALLOC_ARENA_MAX=2, measured together with the lazy
imports (server baseline 136.5 MB before the first run):
| Scenario | fitness_cache_max_entries |
Peak RSS (anon) | Run time | Best fitness |
|---|---|---|---|---|
| base | null (unbounded) |
289.3 MB (231.3) | 89.5 s | −17.473689741050 |
| base | 0 |
179.5 MB (121.1) | 88.7 s | −17.473689741050 |
| with EV | null (unbounded) |
340.0 MB (282.0) | 131.6 s | −13.851295466546 |
| with EV | 0 |
181.3 MB (123.3) | 133.2 s | −13.851295466546 |
Only 6–15 % of the cache lookups hit. A Raspberry Pi 4 with real installation data (see below) showed the same picture: the unbounded cache made a run about 3.5 % faster and cost 100 MB.
Run time on real hardware
Data of a real installation (77 kWh battery, direct marketing, 24 h control horizon at 15 minutes), all settings above:
| Device | Run time | Peak RSS |
|---|---|---|
| Raspberry Pi 4 Model B (2 GB, Cortex-A72), fitness cache off | 623–636 s | 195 MB |
| Raspberry Pi 4 Model B, fitness cache unbounded | 602–609 s | 294 MB |
| x86 VM, 2 vCPU (container, with EV) | 173 s | 127 MB |
On a Raspberry Pi 4 a run therefore takes about 10 minutes. With a 15-minute ems.interval
the device is busy about 70 % of the time; a longer interval leaves more headroom.
Storage writes
With the default database.autosave_interval_sec the measurement and prediction databases are
written every few seconds. On an installation that pushes its forecasts and measurements from
an external system this amounted to 12–14 MB per minute; with autosave disabled about
1 KiB per minute remained. On SD cards and small eMMC this is the main source of wear.
Disabling autosave means that data pushed to EOS is lost when EOS restarts. That is fine when an external system (an energy manager, Home Assistant, Node-RED) pushes forecasts and measurements again; it is not suitable when EOS fetches and keeps its own history.
Allocator
On glibc, Python keeps memory freed after a run in its own pools. Measured on an x86 VM with an
unbounded fitness cache, PYTHONMALLOC=malloc together with the trim thresholds above lowered
the idle memory from about 295 MB to 238 MB, and after each run EOS fell back to about 260 MB
instead of staying at its peak. With the fitness cache disabled the difference is small
(about 5 MB), because the large per-run allocations no longer happen.