Skip to content

perf(android): convert Fabric props directly into JNI - #1860

Merged
mrousavy merged 1 commit into
mrousavy/v1-swift-direct-view-propsfrom
mrousavy/v1-android-direct-view-props
Oct 21, 2026
Merged

mrousavy merged 1 commit into
mrousavy/v1-swift-direct-view-propsfrom
mrousavy/v1-android-direct-view-props

Conversation

@mrousavy

@mrousavy mrousavy commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Stores retained JNI values directly in Fabric props and converts them with the Kotlin JSI converters. C++ interop is generated only for props that actually need it.

Validation: Nitrogen typecheck and full spec generation. Generated output is included.

@vercel

vercel Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
nitro-docs Ready Ready Preview Oct 21, 2026 1:33pm UTC

Request Review

@mrousavy

Copy link
Copy Markdown
Member Author

I measured changed-prop construction and mounting on an ARM64 Android emulator. The benchmark uses the current ReactProp<T> and JSICache, including immutable entry allocation, a snapshot copy, and entry destruction. Both paths call the same cached fbjni AtomicReference.set(Object) method as a controlled setter substitute.

Three fresh process runs, two warmups and seven measured samples per case. The table uses a cached JNIEnv; both cached and uncached contexts were tested. Times are medians of the three process medians; the ratio column gives the per-process STL/direct range.

Changed prop Previous STL path Direct retained JNI path STL/direct ratio
ascii-17 0.175 µs 0.228 µs 0.77-0.92x
unicode-128 1.457 µs 0.259 µs 5.05-6.34x
ascii-4096 14.024 µs 3.297 µs 4.13-4.46x
array-32-strings 4.946 µs 2.100 µs 2.30-2.43x
nested-16x16-strings 34.852 µs 15.204 µs 2.27-2.40x
record-16x8-strings 21.177 µs 10.061 µs 2.08-2.13x

The 17-character ASCII case regresses from about 175 ns to 228 ns. Retaining the JNI value costs more than the tiny STL conversion in that case. Larger strings and containers improve in all three process runs.

The direct path moves Java object creation into prop construction. The mounting phase only borrows the retained reference and calls the setter, around 20-26 ns here. The separate construction measurement includes destruction of each entry, including its global reference. Snapshot copies stay around 3-5 ns.

This excludes the common RawProps lookup/equality check, UI dispatch, Fabric scheduling, and the work done inside a real view setter. An unchanged prop reuses its entry and skips conversion. These numbers measure prop conversion and ownership, not full view latency.

All 180 identity/value checks passed across both cached and uncached JNI contexts. The uncached context also improved for the five larger inputs and regressed for short ASCII. The same binary contains both paths, built with -O2 -DNDEBUG; it uses the latest string converters and frozen Hermes/fbjni libraries.

@mrousavy

Copy link
Copy Markdown
Member Author

Re-ran against 11aa95d5a6 with the current global_ref storage. Short ASCII still regresses in the longer check: 164 ns STL vs 177 ns direct (+7.9%) with a cached JNIEnv. The previous wrapper measured 197 ns in the same binary. Larger inputs remain faster than STL.

The short-ASCII confirmation uses 100,000 operations per sample, three warmups, eleven measured samples, and three fresh process runs. Times below are medians of the three process medians. The overhead range compares STL and global_ref within each process.

17-character ASCII STL Previous wrapper Current global_ref Overhead vs STL
Cached JNI environment 164 ns 197 ns 177 ns +7.9% to +9.6%
Attached, uncached JNI environment 259 ns 409 ns 310 ns +19.3% to +20.5%

The full six-input sweep kept the previous iteration counts: two warmups, seven measured samples, and three fresh processes. Cached-JNI totals:

Changed prop STL (µs) Previous wrapper (µs) Current global_ref (µs) STL / current, per-process range
ascii-17 0.181 0.206 0.169 0.94-1.17x
unicode-128 1.521 0.227 0.192 7.70-8.08x
ascii-4096 14.441 3.399 3.289 4.25-4.39x
array-32-strings 4.915 2.138 2.027 2.37-2.45x
nested-16x16-strings 36.287 16.168 15.579 2.32-2.50x
record-16x8-strings 20.707 9.981 10.164 1.99-2.09x

The short batches were noisy for ASCII: their STL/current ratios crossed 1.0. That prompted the longer check above, where global_ref was 7.9-9.6% slower in all three processes. I would still treat short ASCII as a small regression. Removing the wrapper improves this case, but the measurements do not isolate the cost of each remaining operation.

This measures changed-prop conversion, the actual immutable ReactProp<T> entry and cached JS value, one snapshot copy, one cached AtomicReference.set(Object) call as a setter substitute, and entry destruction. All paths run in the same binary, with rotating measurement order. It excludes RawProps lookup/equality, dispatch, Fabric scheduling, and real view-setter work; unchanged props reuse their entry.

Android 16 / API 36 ARM64 emulator, -O2 -DNDEBUG. Current production converter sources and headers are compiled into the harness; Hermes/fbjni and the Nitro runtime binary remain frozen for comparability. JSICache and BorrowingReference headers are byte-identical to the frozen runtime. No per-call ThreadScope is added to the current path. All 294 identity/value checks passed across both JNI contexts and both batches. Benchmark sources and raw samples remain outside the repo.

Short-ASCII total, per-process medians (ns)
JNI context / process STL Previous wrapper Current global_ref
Cached / 1 157.0 192.5 171.4
Cached / 2 164.0 196.5 177.0
Cached / 3 164.2 201.0 179.9
Uncached / 1 256.7 409.2 309.3
Uncached / 2 259.2 402.6 309.7
Uncached / 3 270.4 424.2 322.5

@mrousavy
mrousavy force-pushed the mrousavy/v1-android-direct-view-props branch from 11aa95d to e8ff58e Compare October 21, 2026 12:16
@mrousavy
mrousavy force-pushed the mrousavy/v1-android-direct-view-props branch from e8ff58e to a5cb707 Compare October 21, 2026 12:30
@mrousavy
mrousavy removed this pull request from stack #1853 October 21, 2026 12:30
@mrousavy
mrousavy added this pull request to stack #1862 October 21, 2026 12:30
@mrousavy
mrousavy force-pushed the mrousavy/v1-android-direct-view-props branch from a5cb707 to 6beb3d2 Compare October 21, 2026 12:48
@mrousavy
mrousavy force-pushed the mrousavy/v1-android-direct-view-props branch from 6beb3d2 to f36ed6e Compare October 21, 2026 13:32
@mrousavy
mrousavy removed this pull request from stack #1862 October 21, 2026 13:32
@mrousavy mrousavy changed the title perf(android): retain direct JNI values in Fabric props perf(android): convert Fabric props directly into JNI Oct 21, 2026
@mrousavy
mrousavy added this pull request to stack #1893 October 21, 2026 13:36
@mrousavy
mrousavy force-pushed the mrousavy/v1-android-direct-view-props branch from f36ed6e to 1a5879e Compare October 21, 2026 15:20
@mrousavy
mrousavy removed this pull request from stack #1893 October 21, 2026 15:20
@mrousavy
mrousavy added this pull request to stack #1913 October 21, 2026 15:21
@mrousavy
mrousavy merged commit f058288 into v1 Oct 21, 2026
0 of 38 checks passed

This branch was successfully deployed

1 active deployment
Preview — 1a5879e3 Deployed Oct 21, 2026 by vercel[bot]
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