Runtime performance
Token cost measures what it costs to build; this page measures what you get. Every implementation was rebuilt in Release configuration from its preserved source tree and profiled on one fixed simulator (iPhone 17 Pro) — no new agent trials were needed, because performance belongs to the artifact, not the agent’s path to it. Raw per-app JSON is in the repository ↗.
The app-size finding was wrong, and reversed on re-measurement
As first published, size came from the Release-iphonesimulator .app directory, which carries an x86_64 slice that never ships and full symbol tables. That penalised LynxJS far more than NativeScript, because its DevTool/DebugRouter stack was counted fat and unstripped at 23.6 MB; in a device archive that stack is 5.3 MB. Re-measured as unsigned arm64 device archives, LynxJS is smaller than NativeScript rather than 50% larger — the conclusion reverses, and the write-up no longer claims footprint as NativeScript’s runtime edge. What survives is the narrower finding that the Sparkling template ships DevTool in Release at all; only its weight was overstated. Token results, memory, launch, and CPU are unaffected. The NativeScript figure is a single reference build: this study predates study-scoped trial branches and its per-trial NativeScript trees were later overwritten by a subsequent run that reused the same branch names. The one surviving spec-v1.0 tree measures 51.1 MB, matching all 8 NativeScript archives in the companion Expo study exactly, so the substitution does not move the number.
| Figure | As published | Corrected |
|---|---|---|
| NativeScript bundle | 93.3 MB | 51.1 MB |
| LynxJS bundle | 140.2 MB | 26.1 MB |
| LynxJS DevTool stack | 23.6 MB | 5.3 MB |
| Headline gap | +50% | −49% |
26.1 vs 51.1 MB; 5.3 MB of the LynxJS side is DevTool.
288 vs 215 MB RSS; footprint 48 vs 38 MB.
316 vs 317 ms; launch-burst settle +7% on Lynx.
All NativeScript apps built untouched; one Lynx app fails Release compilation.
Debug builds carry development-only weight — unoptimized code and, on Lynx, the DevTool/DebugRouter attach path — so they are never measured. Each trial’s final source tree (preserved as a git branch) was rebuilt with -configuration Release and archived. Measuring all implementations rather than one per side turns implementation variance into data: across independently agent-built apps, device-archive size does not vary at all — every lynxjs app measures 26.1 mb — while cold launch varies <6 ms and idle rss <8 mb. runtime characteristics are a property of the framework, not of how the agent wrote the app.
The excluded app: 5/5 NativeScript implementations built in Release untouched; 4/5 LynxJS did — main-lynx-5 fails Release compilation as-written ("Multiple commands produce sample.wav": the agent wired the audio asset through two copy mechanisms, tolerated by Debug, rejected by Release). It fails identically in a device archive. Excluded and disclosed; patching it would invalidate the artifact.
Both bundles are almost entirely runtime, and NativeScript ships the heavier one: an 11.8 MB executable with V8 metadata compiled in, plus a 37.1 MB NativeScript.framework. Lynx ships a 0.8 MB executable over Lynx.framework (14.4 MB), PrimJS (2.1 MB) and its supporting pods. 5.3 MB of the Lynx side is the DevTool/DebugRouter stack the Sparkling template includes even in Release (hatched below; quantified separately so it can be subtracted) — smaller than it looked in a simulator build, but still shipped.
Size is an unsigned arm64 device archive per app (xcodebuild archive, generic/platform=iOS), which runs the same strip and postprocessing an App Store build gets. Uncompressed and before App Store thinning, so these are upper bounds on download size. The LynxJS figures are the median of the same 4 apps measured below; the NativeScript figure is a single reference build, because this study predates study-scoped trial branches and its per-trial NativeScript trees were later overwritten — one spec-v1.0 tree survives. The companion Expo study justifies the substitution: all 8 NativeScript archives there measured 51.1 MB without variation. Memory, launch, and CPU below come from simulator builds and are not comparable to these figures.
| Release, simulator (median) | NativeScript (n=5) | LynxJS (n=4) | Δ |
|---|---|---|---|
| Idle RSS | 215 MB | 288 MB | +34% |
| Idle physical footprint | 38 MB | 48 MB | +26% |
| Cold launch → foreground-active | 316 ms | 317 ms | ~equal |
| Launch-burst settle (CPU quiesce) | 1.53 s | 1.64 s | +7% |
| Idle CPU | 0.0% | 0.0% | equal |
5 cold launches per app, terminated between runs; launch timing from just before invocation to the OS foreground-active marker in the system log, CPU-quiesce fallback recorded alongside. Idle sampled 10×1 s after a 12 s settle. Both frameworks idle clean — the Release DebugRouter is dormant, not polling.
| Feature path (median ms, range) | NativeScript | LynxJS | Verdict |
|---|---|---|---|
| Nav → Health screen rendered | 1,903 (1,784–2,216) | 1,785 (1,769–1,819) | ~tie, Lynx tighter |
| 7-day HealthKit query → rows rendered | 1,059 (1,033–1,081) | 1,085 (1,070–1,113) | tie — 2% apart |
| Log 500 steps → list refreshed | 643 (385–647) | 447 (441–447) | Lynx ~30% faster |
| Nav → Transcribe screen rendered | 1,874 (1,706–2,188) | 1,758 (1,733–1,782) | ~tie, Lynx tighter |
Measured by a generic external UI-test driver tapping every app through its real flows by the spec-pinned labels (agent-built apps untouched), 3 iterations per app, fresh launch per iteration, permission sheets auto-granted. The heavy read path is a dead tie — the OS API dominates. Lynx takes the write→refresh round trip by ~30%, though the single fastest app overall was a NativeScript one (385 ms), so refresh strategy matters as much as framework. No latency metric materially favors NativeScript, and app size does not either once measured on a device archive — its remaining runtime edge is idle memory.
Transcription latency is simulator-blocked identically for both frameworks: SFSpeechRecognizer server recognition rides Siri infrastructure that iOS Simulators lack. All 27 driver iterations across all 9 apps failed to initialize the recognizer, and every app correctly surfaced the error in its status line per spec. It is a physical-device metric — each app displays "Completed in N.N s" on screen.
| Scenario (identical both sides) | NativeScript | LynxJS | Ratio |
|---|---|---|---|
| Pure-JS compute — 2M-iteration math loop | 57 ms | 271 ms | 4.8× |
| JSON round-trip × 500 (100-key object) | 4 ms | 17 ms | 4.3× |
| Native call latency — 1,000 sequential calls | ~1 µs/call | ~130 µs/call | ~130× |
| 10,000 native calls in flight | 5 ms | 1,191 ms | 238× |
| 1 MB string across the boundary, per call | 1.0 ms | 2.0 ms | 2× |
| Checksum 1 MB — pure JS | 74 ms | 444 ms | 6× |
| Checksum 1 MB — native via interop | 3 ms | 9 ms | 3× |
Hand-written bench apps (separate from the agent-built corpus) on the interop-bench branch of each framework repo: identical scenario code both sides, an equivalent native fixture each (ObjC class via direct binding on NativeScript; authored Swift LynxContextModule on LynxJS), self-timed in-app. Every scenario emits a check value that must match across frameworks — all 8 do. Medians of 3 interleaved Release runs plus a verification run rebuilt from the committed sources.
- “Dial to native” is real on both frameworks: the same task moved from JS to native got 24× faster on NativeScript and 49× faster on LynxJS. Any hot path can be rewritten natively and called from JS on either side.
- The cost of reaching native differs by two orders of magnitude: ~1 µs per call via NativeScript direct bindings vs ~130 µs through LynxJS’s async module bridge — and pipelining 10,000 calls does not amortize it. This matters for chatty per-item native access and washes out for bulk transfers (1 MB marshalling is near-parity) and coarse-grained calls like the feature paths above.
- The JS engines are a real trade: identical code runs ~5× faster on V8 (NativeScript) than PrimJS (LynxJS) in these compute and JSON workloads — PrimJS optimizes for startup footprint, not throughput.
- Neither framework is slow when used to its architecture: LynxJS wants coarse-grained, batched native calls with hot loops in native; NativeScript tolerates fine-grained native chatter and requires no bridge authoring to reach native at all.
- iOS Simulator on one Mac: absolute numbers are not device numbers — only the comparative readings transfer, and they share host, runtime, and interleaved scheduling.
- No scroll/FPS metric: the spec’d app has no scroll-stressing surface, and Lynx’s multithreaded-rendering pitch targets exactly that kind of load. That claim is explicitly untested here — not quietly folded in.
- JS↔native bridge micro-latency is not separately instrumented (that would require modifying the agent-built apps); it shows up indirectly in the tap-to-result timings, where it proved immaterial.