summaryrefslogtreecommitdiff
path: root/docs/PERFORMANCE.md
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-21 03:55:15 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-21 03:55:15 +0200
commitf439beec17419ccbe0dc7643cc039079f15e04a0 (patch)
tree02863a95ba3d8af263a68586370319e5559b00a8 /docs/PERFORMANCE.md
parent56e0c351bb95cfdf63aa76ab8c5d725a694bad63 (diff)
docs: user guide, known limitations and M8 results; bump to 0.8.0v0.8.0
0.8.0 is the release candidate: 1.0.0 is tagged after the on-watch checklist (docs/TESTING.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/PERFORMANCE.md')
-rw-r--r--docs/PERFORMANCE.md47
1 files changed, 47 insertions, 0 deletions
diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md
index 9e89e40..4c35be6 100644
--- a/docs/PERFORMANCE.md
+++ b/docs/PERFORMANCE.md
@@ -22,9 +22,56 @@ The targets are in [README section 18](../README.md#18-performance-budgets). Thi
| `startup_ms` | 100 ms | M4 measured 1 to 18 ms; the margin absorbs simulator jitter |
| `first_frame_wall_ms` | 36000 ms | Regression guard only: about 2× M4's 16-18 s, which include loading the glance and opening the app from it |
| `mem_used_fraction` | 70 % | Hard limit, leaves room for spikes on the 512 KB watches |
+| `glance_mem_used_fraction` | 70 % | Hard limit on the glance's 64 KB (README 18), measured after its first draw |
+| Watchdog | Each job step at most half the per-event limit | `make watchdog`: the largest inputs at two steps per event (M8) |
## Results
+### M8, 2026-10-21 (polish and release candidate, version 0.8.0)
+
+- **Memory, Forerunner 255S (512 KB):**
+
+ | | M7 | M8 | Budget |
+ | --- | --- | --- | --- |
+ | First frame (`make memtest`) | 31.4 % | 34.4 % | 70 % |
+ | Peak during the 100-entry import (UI flow) | 56.2 % | 45.3 % | 70 % |
+ | Glance, entry count / favourite code | about 19 % (12.5 KB) | 36.7 % / 42 % | 70 % |
+
+ The import peak fell because parsed items are kept packed (an `Entry` object takes about 490 bytes, an item with its entry about 760; packed, about 110) and the pasted text is held once. The first frame rose by the code M8 added. Most of the 156 KB the app occupies before it draws anything is its own code and constant data. The glance now loads the crypto for the favourite code. The 768 KB watches: 22.9 % at the first frame, 30.3 % at the import's peak (37.6 % in M7), 37 % in the glance.
+- **Import:** 401 ms of compute for 100 entries in the unit-test benchmark (332 ms in M5, before packing). In the simulated watches it took 7.65 s on the 255S and 3.8 s on the 768 KB watches, which now run two job steps per tick (7.6 s in M7).
+- **QR compute** (unit-test benchmark, simulator, one step at a time):
+
+ | QR | M6 | M8 |
+ | --- | --- | --- |
+ | Version 3 | 81 ms, 28 steps | 61 ms, 54 steps |
+ | Version 10 | 312 ms, 105 steps | 139 ms, 106 steps |
+ | Version 26 | 1.4 s, 407 steps | 482 ms, 391 steps |
+
+ Mask scoring now works on packed rows and columns (16 modules per word). The step counts didn't fall, because M8 found that M6's steps were too large for big codes (below) and sized every step by measurement. At 50 ms per tick, a version 10 QR needs at least 5.3 s on the 255S and 2.7 s on the 768 KB watches, plus the watch's compute, which only the on-watch benchmark can tell. README 18's 3 s target is met on the 768 KB watches at best; the cache makes every later opening instant.
+- **The demo cards in the simulated watches** (first encoding, `make screenshots`):
+
+ | Code | Forerunner 255S | 768 KB watches |
+ | --- | --- | --- |
+ | QR version 6 | 3.9 s (2.8 s in M6) | 2.0 s |
+ | Data Matrix 18 x 18 | 0.8 s | 0.4 s |
+ | PDF417 | 1.3 s | 0.4 to 0.7 s |
+ | Aztec 23 x 23 | 0.5 s | 0.25 s |
+
+ The 255S is slower than in M6 because its steps are smaller: M6's were sized by estimate and only exercised with these small codes.
+- **Watchdog (`make watchdog`).** M6's step sizes were never tried with large codes. With the largest inputs the app accepts, every 2D encoder tripped the watchdog on the 255S. Going by the measured costs, the biggest steps (QR version 40's single-step mask application, about 31,000 modules) were far over the 768 KB watches' limit too. A code like that, added from the phone, would have stopped the app during settings processing at every start. A loop iteration turned out to cost 50 to 70 counted bytecodes, 2 to 3 times the estimate. Every step is now sized so that two steps fit in one event. Results on the 255S (steps, and peak memory in the stress build, which uses 204 KB before the first code):
+
+ | Input | Symbol | Steps | Peak |
+ | --- | --- | --- | --- |
+ | QR, 2,900 letters, level L | version 40, 177 x 177 | 1,041 | 316 KB (62 %) |
+ | QR, 800 digits, level H | 93 x 93 | 255 | 237 KB |
+ | Data Matrix, 1,500 letters | 144 x 144 | 446 | 276 KB (326 KB before the byte-array grid) |
+ | Aztec, 1,400 letters | 95 x 95 | 254 | 231 KB |
+ | PDF417, the most this screen takes | 120 x 48 | 50 | 222 KB |
+
+ In the real app, which uses about 178 KB at the list, the largest QR would peak at about 58 %. At 50 ms per step it takes about 52 s on the 255S and 26 s on the 768 KB watches, once, and it can't be shown on the 255S's screen anyway.
+- **Drawing.** Codes are drawn as runs of dark modules, one rectangle each. Up to 1,500 runs per watchdog unit are drawn directly in `onUpdate`. Larger codes (the Venu X1 fits a version 39 QR with 8,111 runs) are painted into a buffered bitmap in steps first; `make watchdog` decodes one from the screen.
+- **Start-up:** `startup_ms` 1 to 17 ms and launch to first frame 16 to 18 s, unchanged.
+
### M7, 2026-10-20 (settings forms, version 0.7.0)
- **Memory is rising with the code.** The app's code is loaded into the same memory as its data, so every milestone adds to it: