diff --git a/docs/akkudoktoreos/smalldevices.md b/docs/akkudoktoreos/smalldevices.md new file mode 100644 index 00000000..369819c5 --- /dev/null +++ b/docs/akkudoktoreos/smalldevices.md @@ -0,0 +1,116 @@ +% 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` [^pr1376] | −110 to −160 MB peak per run | identical | +| `general.timezone_override` [^pr1377] | −28 MB at startup (no timezone lookup) | identical | +| `optimization.self_consumption_interpolator: numpy` [^pr1378] | −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 | + +[^pr1376]: Added by [#1376](https://github.com/Akkudoktor-EOS/EOS/pull/1376). +[^pr1377]: Added by [#1377](https://github.com/Akkudoktor-EOS/EOS/pull/1377), which also imports + optional heavy packages on first use (−123 MB at startup without any configuration change). +[^pr1378]: Added by [#1378](https://github.com/Akkudoktor-EOS/EOS/pull/1378). + +Example configuration: + +```json +{ + "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): + +```ini +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. diff --git a/docs/index.md b/docs/index.md index 10c80e05..999a0d79 100644 --- a/docs/index.md +++ b/docs/index.md @@ -31,6 +31,7 @@ develop/install.md akkudoktoreos/configuration.md develop/update.md akkudoktoreos/upgrade-genetic.md +akkudoktoreos/smalldevices.md develop/revert.md akkudoktoreos/adapter/adapterhomeassistant.md akkudoktoreos/adapter/adapternodered.md