Last updated: 2026-05-18 Primary benchmark build: 0.4.0 release build, commit f26a103.

Replay Control is designed to run quietly on a Raspberry Pi while still handling large game libraries. The practical result from the 0.4.0 measurements is:

  • Normal browsing is fast. Home renders in about 10 ms, game pages in 1-3 ms, and common searches in about 26-31 ms on a Pi 5.
  • Idle memory is small after startup settles: roughly 45-61 MB on the measured USB library.
  • Light browsing stays modest: about 74-77 MB and about 0.6% of one CPU core when no game is running.
  • Heavy artificial load raises memory temporarily, but the service remains responsive and memory drops back afterward.
  • Large NFS libraries are slower to scan and search, but the app remains usable while maintenance work continues.

The main 0.4.0 numbers below use USB storage with 23,666 games. A separate NFS section records the larger 102 K+ ROM development library.

Tested Hardware

PlatformStatus
Raspberry Pi 5, 2 GB RAMMeasured
Raspberry Pi 4Measurements pending

Everyday Use

CPU Use

CPU is reported as percent of one core. On a Pi 5, 100% means one core is fully busy.

StatePi 5Pi 4
Idle, no requests0.03%pending
One user browsing, no game running0.60%pending
Heavy concurrent loadsee Stress Testspending

Memory Use

Memory is the resident set reported by Linux for the replay-control process.

StatePi 5 memory in usePi 5 peakPi 4
After startup scan completed, before browsing45-61 MB62-66 MBpending
Immediately after light browsing, no game running77 MB78 MBpending
60 s after light browsing, no game running74 MB78 MBpending
Immediately after full load test, no game running155 MB369 MBpending
60 s after full load test, no game running122 MB369 MBpending

The peak column is Linux’s high-water mark for the process. It does not go down until the process restarts, even after current memory has dropped.

Startup

On an unchanged USB library, startup verification completed in 1.4 s after the service restart used for the memory test. The app settled into a roughly 45-61 MB idle range once startup work was finished.

First scans and full rebuilds do more work because they must discover files, enrich metadata, queue thumbnails, and, for hash-matched systems, identify ROMs by CRC. Those longer operations are covered in Library Maintenance on NFS.

Page Load Times

Warm, single-user page requests on Pi 5 with USB storage:

PageTypical server timeNotes
Home9.3 msMain library view
NES games2.5 msSystem list page
Arcade games2.0 msSystem list page
Game detail1 msFrom load test, c=1 median
Search “mario”29 msCommon search
Search “sonic”30 msCommon search
Search “street fighter”26 msMulti-word search
Search “a”158 msBroad worst-case search

Download Sizes

The web app’s static files. WASM is served gzip-compressed by the server.

FileRawGzip
WASM bundle4,630 KB1,014 KB
CSS98 KB16 KB
Home HTML57 KB-
NES games HTML22 KB-

USB Stress Tests

These numbers come from Apache Bench (ab) issuing 50 requests per endpoint. This is intentionally heavier than normal use; it is a regression and robustness check.

Concurrent Throughput

Homepage

ConcurrencyReq/sP50P95
19.77 ms8 ms
5253.019 ms24 ms
10250.238 ms45 ms
20255.272 ms85 ms
30263.297 ms119 ms

The c=1 homepage run included one long outlier, so Req/s is not representative there. The median and P95 are the useful values for that row.

Queryc=1 Req/sc=1 P50c=1 P95c=10 Req/sc=10 P50c=10 P95
“mario”34.129 ms31 ms36.9264 ms289 ms
“sonic”32.830 ms34 ms36.7268 ms284 ms
“street fighter”38.626 ms29 ms55.5171 ms192 ms
“a”6.2158 ms166 ms9.41,056 ms1,172 ms

System and Game Pages

Endpointc=1 Req/sc=1 P50c=1 P95c=10 Req/sc=10 P50c=10 P95
SNES games638.12 ms2 ms1,350.57 ms9 ms
Mega Drive games664.81 ms2 ms1,338.67 ms10 ms
Game detail682.01 ms2 ms1,623.95 ms8 ms

Mixed Concurrent Test

Four endpoints were hit at the same time, each with concurrency 5.

EndpointReq/sP50P95
Home26.1204 ms216 ms
Search “mario”13.8367 ms384 ms
Search “sonic”13.3378 ms408 ms
Search “street fighter”13.1378 ms409 ms

Library Maintenance on NFS

NFS is a harder workload because scans must walk a remote ROM tree and rebuilds may stream large files to recompute CRCs. Current builds keep catalog.sqlite, library.db, and external_metadata.db on the Pi, so normal page rendering is much less tied to NFS latency than library maintenance is.

Earlier NFS library maintenance measurements on a 95,495-ROM development library:

OperationDurationHash behavior
Startup cache verification, already fresh~4.5 s from service startNo system rescan needed
Manual rescan194.1 sReused 17,490 exact CRC cache entries and 16 same-size entries; recomputed 2 hashes
Manual rebuild636.0 sForced 17,508 CRC reads; skipped 2 CD/image entries in hybrid folders

0.4.0 validation on a larger 99,964-ROM NFS library measured the deferred-identity pipeline:

OperationDurationHash behavior
Foreground populate280.1 sReconciled every visible system and enriched rows before identity finished
Background identity437.9 sForced 19,019 hash-eligible rows through two 200-row workers
End-to-end build718.0 sLibrary remained browsable while identity continued

Follow-up validation on a 102,662-ROM NFS library measured a normal manual rescan after identity had already completed:

OperationDurationHash behavior
Manual rescan313.3 sCached identity reused; 12 hash-eligible systems skipped because no rows needed matching

The key result is responsiveness. The foreground library becomes available before the hash tail finishes, and the app stays usable during the remaining NFS reads.

NFS Large-Library Serving

Measured on Pi 5 against the 102 K+ ROM NFS development library after deploying the same 0.4.0 release build from commit f26a103.

Warm Page Requests

PageTTFBTotalResponse size
Home7.7 ms8.0 ms8.6 KB
NES games2.0 ms9.0 ms10.9 KB
Arcade games2.0 ms9.4 ms9.6 KB

NFS Load Test

Search is much heavier here than on USB because the NFS development library is more than four times larger.

Endpointc=1 Req/sc=1 P50c=1 P95c=10 Req/sc=10 P50c=10 P95
Home148.57 ms8 ms282.233 ms40 ms
Search “mario”9.5104 ms111 ms10.2966 ms1,042 ms
Search “sonic”9.0111 ms115 ms9.61,015 ms1,098 ms
Search “street fighter”5.494 ms98 ms14.2682 ms768 ms
Search “a”1.3800 ms839 ms1.85,410 ms5,888 ms
SNES games643.51 ms2 ms1,382.87 ms9 ms
Mega Drive games646.71 ms2 ms1,414.16 ms10 ms
Game detail710.01 ms2 ms1,593.66 ms8 ms

Memory around the same run:

StateMemory in usePeak since service start
Idle after release install55 MB90 MB
Immediately after full load test157 MB967 MB
60 s after full load test157 MB967 MB

The high-water mark came from artificial broad-search stress on a very large library. It is not a normal browsing footprint, but it is useful regression data: the process stayed running and served every request class through the stress run.

Historical Comparison

The current 0.4.0 release is dramatically faster than the original unoptimized implementation and remains comfortably within Pi 5 limits for normal use.

MetricPre-optimizationv0.2.0v0.3.00.4.0
Home page, warm c=1940 ms19 ms14 ms9 ms
Search “mario”, c=1348 ms63 ms47 ms29 ms
Search “a”, c=1--194 ms158 ms
Mixed homepage stress0.60 req/s8.3 req/s11.8 req/s26.1 req/s
Steady memory after startup324 MB67 MB67 MB45-61 MB
WASM gzip-1,778 KB995 KB1,014 KB
Incremental build time~90 s~90 s~90 s~10 s

Older intermediate 0.4.0 validation runs had slightly faster home stress throughput and a smaller WASM bundle. The final 0.4.0 numbers above are the release baseline because they include the completed library-build, metadata, and startup-scan changes.

Test Methodology

  • CPU: tools/pi-cpu.sh reads /proc/<pid>/stat and reports CPU relative to one core. --browse simulates one user clicking through home, system pages, search, game detail, and manuals every ~2 s.
  • Memory: tools/pi-memory.sh reads /proc/<PID>/status: VmRSS (memory in use), VmHWM (peak since process start), and RssAnon.
  • Page and asset benchmarks: tools/bench.sh, with Lighthouse skipped for the recorded release runs.
  • Stress tests: tools/load-test.sh, which uses Apache Bench (ab) with 50 requests per endpoint.
  • All current USB measurements were taken on a freshly restarted Pi 5, USB storage, no game running, default jemalloc configuration.
  • ab’s “Failed” column counts response-size variance, not HTTP errors. All requests returned successfully.
  • Raw results are stored in tools/bench-results/.