Skip to content

perf: dev-mode parity gap vs Workspace mobile (sub-perception) #25

@LeslieOA

Description

@LeslieOA

Observation

Side-by-side dev builds (expo run:ios --device / mobile:ios:device) on the same iPhone 17 Pro, same hesprs-demo fixture, same SDK 55 stack, the Workspace mobile app feels very slightly snappier than our playground. No dropped frames in Workspace; occasional minor hitching in the playground.

Release builds (ios:run:device:release) are exponentially better in the playground and at least at parity, plausibly faster, than Workspace mobile. So the gap is dev-mode specific.

What we already aligned (this is why the gap is small)

  • Same renderer source (verified byte-identical via diff)
  • Same SDK (Expo 55)
  • Same React (19.2.0), RN (0.83.x patch only)
  • Same Skia, Reanimated, Worklets, Gesture-Handler versions
  • Same babel pattern (worklets plugin loaded via Node resolution, preset auto-detect disabled)
  • Same expo-dev-client
  • Same experiments.reactCompiler: true
  • Same react-native-safe-area-context

What we did NOT replicate (because none should plausibly affect canvas dev perf)

  • expo-router + react-native-screens (no navigation in playground)
  • expo-image, expo-symbols, expo-glass-effect (cosmetic, not on the render path)
  • expo-splash-screen (launch-time only)
  • zustand state management (no app state)
  • react-native-enriched-markdown (renderMarkdown is vestigial — TextNodeContent returns null and text always renders via Skia)
  • iOS infoPlist UTI registration for .canvas (we load from inline fixture)
  • with-canvas-uti Expo config plugin

Theories not investigated

  1. Stable component-instance identity. Workspace's screen lives under expo-router's Stack.Screen which provides a stable parent instance; our App.tsx is the root component re-rendering on every setLastAction. React Compiler should mostly handle this, but possibly not perfectly.
  2. Native-thread scheduling differences from expo-router. Router-rendered screens get screen-stack native rendering on iOS via react-native-screens — could change CoreAnimation scheduling priorities.
  3. Hermes snapshot differences. The two apps have different module graphs; Hermes' inline cache hit/miss patterns differ.
  4. Metro fast-refresh module-id stability. Workspace's larger bundle may produce more stable module IDs across reloads, reducing HMR churn.

Why this is parking-lot

  • The gap is sub-perception in the conversation that produced it ("ever so slightly snappier") and is dev-mode only.
  • Release-build perf is what ships and that's at or above parity.
  • Profiling would be required to characterise the gap concretely; gut estimates are not reliable here.
  • Every alignment we tried that could plausibly close it has been applied. Further chasing is yak-shaving until there's a concrete bottleneck to point at.

When to revisit

  • If a real consumer of @workspace.sh/react-native-jsoncanvas reports a similar dev-mode perf issue
  • After the Rust core port (feat(core): port pure-logic core to Rust (JSI native + wasm-bindgen web) #6) when render-path JS thread work changes shape — would be a natural moment to re-profile
  • If React Compiler upgrades or new Reanimated minors land that change the dev-mode baseline

How to investigate if revisited

  1. Attach React DevTools Profiler in both apps, capture 5s of identical pan+pinch on the same fixture
  2. Compare commit count, commit duration, scheduling lane breakdown
  3. Use Hermes sampling profiler (expo run:ios --configuration Debug with profiler enabled) to capture JS-thread time per app
  4. If the gap is GPU/native, capture Xcode Instruments → Core Animation → frame-time deltas

Refs

Surfaced during PR #22 (Expo playground). Mentioned in commit 4b7f127's body.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions