# Performance The targets are in [README section 18](../README.md#18-performance-budgets). This document records how each number is measured, what it does and doesn't tell you, and the results. ## How it is measured - **In-app start-up** (`startup_ms`): debug builds take `System.getTimer()` when the app object is created and again after the first complete frame, and print the difference (`source/util/Diag.mc`). It doesn't include the time the watch needs to load the program before the app object exists. - **Launch to first frame** (`first_frame_wall_ms`): `docker/scripts/app-diag.sh` measures the wall-clock time from launching `monkeydo` until the app's first-frame line appears. This includes the Java start of `monkeydo`, sending the program to the simulator and loading it, so it is mostly tool overhead; it's only useful to catch large regressions. - **Memory** (`mem_used_fraction`): `System.getSystemStats()` after the first frame: used memory divided by the memory the device gives watch apps. - **On the watch**: the numbers that matter are measured on your Forerunner 70 with a sideloaded debug build that shows them on screen. That arrives with M4; the simulator runs on your PC's CPU and says nothing about watch speed. `make perf` and `make memtest` fail when a value exceeds `tools/budgets.json`. ## Budgets in force | Value | Budget | Why | | --- | --- | --- | | `startup_ms` | 3000 ms | Placeholder until M4 sets about 2× the measured baseline | | `first_frame_wall_ms` | 15000 ms | Regression guard only (tool overhead dominates) | | `mem_used_fraction` | 70 % | Hard limit, leaves room for spikes on the 512 KB watches | ## Results ### M3, 2026-10-19 (version 0.3.0, storage) `test/BenchTest.mc` with an in-memory store, simulator in Docker, Forerunner 70 profile: | Operation | Time | Notes | | --- | --- | --- | | Read 20 entry headers | 6.8 ms | 0.34 ms per header, mostly TLV decoding in Monkey C | | Open one entry (verify + decrypt) | 1.05 ms | Device-key entry | | Add and remove one entry | 1.4 ms | Seal, two metadata writes | Record sizes: 239 bytes for a typical OTP entry, 208 for a short code, 4,279 for the largest allowed payload. `make perf` and `make memtest`: in-app start-up 1 to 6 ms, and launch to first frame 10.5 s, unchanged. Memory after the first frame rose to 4.0 % (768 KB watches) and 5.9 % (Forerunner 255S), because the debug program is larger; the app doesn't open the vault yet. The signed package for 72 products is 2.5 MB. Consequence for M4: reading 150 headers would take about 50 ms in the simulator, and on the watch probably many times that. The list will cache headers and read only the visible rows when it opens, instead of reading all of them at start-up. ### M2, 2026-10-18 (version 0.2.0, crypto and OTP primitives) `test/BenchTest.mc`, simulator in Docker, average per call (identical within noise on all four test devices): | Operation | Time | Notes | | --- | --- | --- | | TOTP, SHA-1 | 0.35 ms | HMAC built on the native SHA-1 | | TOTP, SHA-256 | 0.05 ms | Native HMAC-SHA256 | | TOTP, SHA-512 | 17.5 ms | SHA-512 in Monkey C; was 27.9 ms before inlining the rotations | | PBKDF2, 1000 iterations | 52 ms | Was 89 ms before removing the per-iteration wipe; native HMAC alone is about 3 ms | | Seal + open a record | 0.25 ms | AES-256-CBC + HMAC-SHA256, two HKDF key derivations cached | | Start-up self-test | 6.7 ms | All known-answer tests | | PBKDF2 calibration for 1 s | about 12,000 iterations | In the simulator; the watch will choose far fewer | `make perf` and `make memtest` with the larger app: in-app start-up 1 to 7 ms; launch to first frame rose from 6.5 s to 10.5 s, because `monkeydo` now sends a larger debug program (crypto code and debug information), still under its 15 s guard. Memory after the first frame: 2.3 % (768 KB watches) to 3.4 % (Forerunner 255S). The signed package for 72 products is 1.5 MB. What this means for the watch, to be confirmed with the on-watch benchmark in M4: the simulator runs on a desktop CPU, so absolute times will be many times higher on the Forerunner 70. The ratios hold: SHA-512 entries cost about 50 times as much as SHA-1 entries, and Monkey C loop overhead, not the native HMAC, dominates PBKDF2. If the watch manages fewer than 2,000 PBKDF2 iterations per second, unlocking takes longer than the 1 s target (the lower bound is kept for security); M8 can move more of the XOR work into native calls. ### M0, 2026-10-18 (version 0.1.0, placeholder app) Simulator in Docker on your PC (12 cores), debug builds: | Device | Watch-app memory | In-app start-up | Launch to first frame | Memory used | | --- | --- | --- | --- | --- | | Forerunner 255S | 512 KB | 1 ms | 6.4 s | 1.3 % | | Venu Sq 2 | 768 KB | 3 ms | 6.5 s | 0.9 % | | Forerunner 70 | 768 KB | 0 ms | 6.7 s | 0.9 % | | Forerunner 965 | 768 KB | 6 ms | 6.8 s | 0.9 % | The placeholder app draws two lines of text, so these numbers only prove the measuring works. The first meaningful baseline comes with M4 (the real list and code screens). The signed package is 488 KB for all 72 products; a release build for one device is 7 to 15 KB.