Skip to content

feat(example): add animation benchmark demo#10

Merged
janicduplessis merged 8 commits intomainfrom
@janic/add-benchmark
Mar 20, 2026
Merged

feat(example): add animation benchmark demo#10
janicduplessis merged 8 commits intomainfrom
@janic/add-benchmark

Conversation

@janicduplessis
Copy link
Collaborator

@janicduplessis janicduplessis commented Mar 20, 2026

Description

There's no way to quantitatively compare the per-frame overhead of react-native-ease against other animation approaches. The existing Comparison demo is visual-only (lag switch), which shows resilience but not cost.

Solution

Adds a Benchmark screen to the example app that measures actual per-frame animation overhead across 4 approaches:

  • react-native-ease — native CAAnimation (iOS) / ObjectAnimator (Android)
  • Reanimated shared value — worklet-driven withTiming
  • Reanimated CSS — CSS keyframes API
  • RN AnimatedAnimated.timing with useNativeDriver: true

All approaches run the same animation (continuous translateX loop, linear, 2s) on a configurable number of views (10–500).

Frame timing is collected via a new frame-metrics Expo local module:

  • Android: Window.OnFrameMetricsAvailableListener — reports per-frame ANIMATION_DURATION, LAYOUT_MEASURE_DURATION, DRAW_DURATION. Primary metric is UI thread time (anim + layout + draw sum) with correct per-frame percentiles.
  • iOS: Swizzles CADisplayLink factory to wrap every callback with timing. Aggregates total callback duration per frame using display link timestamp. This captures the actual work done in animation framework callbacks each frame.

Preliminary Results

All results are release builds. Reanimated results shown with experimental feature flags both OFF (default) and ON (ANDROID/IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS, USE_SYNCHRONIZABLE_FOR_MUTABLES, DISABLE_COMMIT_PAUSING_MECHANISM). FF = feature flags enabled.

Android (release build, emulator, M4 MacBook Pro)

Android benchmark

Detailed numbers

UI thread time per frame: anim + layout + draw (ms)

Views Metric Ease Reanimated SV Reanimated SV (FF) Reanimated CSS Reanimated CSS (FF) RN Animated
10 Avg 0.21 1.15 0.75 0.99 0.45 0.36
10 P95 0.33 1.70 1.53 1.44 0.80 0.62
10 P99 0.48 1.94 2.26 1.62 1.35 0.98
100 Avg 0.36 2.71 1.81 2.19 1.01 0.71
100 P95 0.56 3.09 2.29 2.67 1.91 1.08
100 P99 0.71 3.20 2.63 2.97 2.25 1.36
500 Avg 0.60 8.31 5.37 5.50 2.37 1.60
500 P95 0.75 9.26 6.36 6.34 2.86 1.88
500 P99 0.87 9.59 6.89 6.88 3.22 3.84

UI Thread Breakdown (avg ms, 500 views)

Approach Anim Layout Draw
Ease 0.52 0.05 0.02
Reanimated SV 8.25 0.04 0.02
Reanimated SV (FF) 5.31 0.04 0.02
Reanimated CSS 5.44 0.04 0.02
Reanimated CSS (FF) 2.31 0.04 0.02
RN Animated 1.54 0.04 0.02

iOS (release build, simulator, iPhone 16 Pro, M4 MacBook Pro)

iOS benchmark

Detailed numbers

Display link callback time per frame (ms)

Views Metric Ease Reanimated SV Reanimated SV (FF) Reanimated CSS Reanimated CSS (FF) RN Animated
10 Avg 0.01 1.33 1.08 1.06 0.63 0.83
10 P95 0.02 1.67 1.59 1.34 1.01 1.18
10 P99 0.03 1.90 1.68 1.50 1.08 1.31
100 Avg 0.01 3.72 3.33 2.71 2.48 3.32
100 P95 0.01 5.21 4.50 3.83 3.39 4.28
100 P99 0.02 5.68 4.75 4.91 3.79 4.55
500 Avg 0.01 6.84 6.54 4.16 3.70 4.91
500 P95 0.01 7.69 7.32 4.59 4.22 5.66
500 P99 0.02 8.10 7.45 4.71 4.33 5.89

Note: Ease shows ~0ms on iOS because CAAnimation runs entirely on the Core Animation render server, off the main thread. On Android, ObjectAnimator runs on the UI thread but is significantly lighter than going through the React Native shadow tree (Reanimated) or the JS-driven native animation driver (RN Animated).

Feature flags improve Reanimated performance significantly on Android (up to ~57% reduction for CSS at 500 views) by bypassing the React Native shadow tree for UI prop updates. iOS improvement is more modest (~10-19% for SV, ~10-40% for CSS).

Test plan

  • Run example app on iOS simulator/device and Android emulator/device
  • Navigate to Advanced → Benchmark
  • Select a view count (e.g. 100), tap an approach button or "Run All"
  • Verify animations render for ~5 seconds, then results appear in the table
  • Compare frame times across approaches at different view counts

Add a benchmark screen that measures per-frame UI thread overhead
across 4 animation approaches: ease, reanimated shared value,
reanimated CSS, and RN Animated.

Uses an Expo local module (frame-metrics) wrapping:
- Android: FrameMetrics API (ANIMATION_DURATION, LAYOUT_MEASURE_DURATION,
  DRAW_DURATION) for precise per-component breakdown
- iOS: CADisplayLink for frame delivery timing

Configurable view count (10-500) with results showing avg/p95/p99
of UI thread time per frame, plus Android-specific breakdown table.
- Add podspec for iOS autolinking
- Swizzle CADisplayLink factory to measure callback duration per frame
- Aggregate per-frame callback time using display link timestamp
- Make UI thread time fields non-optional (both platforms report them)
- Update iOS label to "Display link callback time per frame"
…lags

Add benchmark charts comparing per-frame overhead of Ease vs Reanimated
(SV/CSS) vs RN Animated on both Android and iOS.

Enable Reanimated experimental feature flags in the example app for fair
comparison (SYNCHRONOUSLY_UPDATE_UI_PROPS, USE_SYNCHRONIZABLE_FOR_MUTABLES,
DISABLE_COMMIT_PAUSING_MECHANISM).
Remove unused return values (avgFrameTime, p95/p99FrameTime,
droppedFrames, totalFrames, frameDurations, p95/p99AnimationTime)
from both native modules and TypeScript types. Also remove the
separate CADisplayLink for frame delivery timing on iOS since
we only use the swizzle-based callback measurement.
@janicduplessis janicduplessis merged commit a6d6eaf into main Mar 20, 2026
4 checks passed
@janicduplessis janicduplessis deleted the @janic/add-benchmark branch March 20, 2026 09:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant