You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit f78bdd4
Browse filesBrowse the repository at this point in the historyBrowse files
Follow-up to #59414: cache each Java TurboModule method's ID with its signature, by name
## Summary:
This is a follow-up to #59414, suggested by @javache in [its review](#59414 (comment)): keep a method's `jmethodID` in the same per-module cache as its parsed JNI signature, and index that cache by method name, instead of having two caches.
Before this change, a Java TurboModule method's ID was cached by its caller: every generated host function kept a `static jmethodID cachedMethodId` and passed it to `invokeJavaMethod` by reference, and `JavaInteropTurboModule` kept a `methodIDs_` vector for the same purpose. `JavaTurboModule` then used that ID as the key of its `MethodInfo` cache (#59414).
With this change:
- `JavaTurboModule::MethodInfo` also holds the method's `jmethodID`, and `methodInfos_` is keyed by the method name (a `std::string`, looked up through a `std::string_view` without allocating). On a method's first call, `getMethodInfo` looks the method up with `GetMethodID` and parses its signature. A signature that doesn't match still turns into a JS error (redbox) before anything is called, and nothing is cached for it.
- `invokeJavaMethod` gets an overload that takes the method name and JNI signature as C strings and no `cachedMethodID`. Generated code passes string literals, so a call no longer builds two `std::string`s; the JNI signature is usually longer than the small-string buffer, so that was a heap allocation on every call.
- The previous overload (`const std::string&` name and signature, `jmethodID&`) is kept, for code generated by earlier codegen versions and for modules that call it directly, and marked `[[deprecated]]`. It forwards to the new one; `cachedMethodID` is no longer read or written. Libraries that ship pre-generated JNI code build it with `-Wall -Werror` (the codegen CMake template applies `target_compile_reactnative_options`), so their builds will fail on the deprecation warning until they regenerate that code.
- The codegen template for Java module JNI files (`GenerateModuleJniCpp`) no longer emits `static jmethodID cachedMethodId`, and `JavaInteropTurboModule` drops `methodIDs_`.
Besides one cache instead of two, every generated host function gets smaller: RNTester's `libreactnative.so` (which includes `FBReactNativeSpec`) is 61 KB smaller and its `libappmodules.so` 9.4 KB smaller.
## Changelog:
[ANDROID] [CHANGED] - Java TurboModules cache each method's ID with its parsed signature, by name, instead of in every generated host function
[ANDROID] [DEPRECATED] - `JavaTurboModule::invokeJavaMethod` with a `cachedMethodID` argument; use the overload without it
## Test Plan:
- `yarn jest packages/react-native-codegen` passes (64 suites, 3,130 tests); the `GenerateModuleJniCpp` snapshots are updated for the template change.
- `node scripts/clang-format.js --check` reports no changes for the touched C++ files.
- C++ API snapshots: the new `invokeJavaMethod` overload is added to the four `ReactAndroid*Cxx.api` snapshots by hand, where the generator sorts it; `scripts/cxx-api` can't run on current `main` locally (its codegen step fails with `SyntaxError: Unexpected token '{'` while processing `FBReactNativeSpec`, without this change too).
- `./gradlew :packages:rn-tester:android:app:assembleRelease -PreactNativeArchitectures=arm64-v8a` succeeds with this change; RNTester's generated `FBReactNativeSpec` and app module code use the new overload.
- **On a device.** Two RNTester release APKs built from the same local checkout (`main` at f7de203 with local benchmark screens), without and with this change, installed alternately on a moto g57 power (Android 16), three installs each with two launches. A Playground screen times synchronous calls to `NativeSampleTurboModule` (20,000 calls each, 5,000 for void, callback and promise calls), median of the six launches:
| Call (JNI signature) | before | this PR | change |
|---|--:|--:|--:|
| `getBool` (`(Z)Z`) | 1.10 µs | 1.15 µs | +4% |
| `getNumber` (`(D)D`) | 1.46 µs | 1.53 µs | +5%, ranges overlap |
| `getString` (`(Ljava/lang/String;)Ljava/lang/String;`) | 2.05 µs | 1.92 µs | −6% |
| `getArray`, 3 items | 10.34 µs | 10.06 µs | −3% |
| `getObject`, 3 keys | 9.90 µs | 9.58 µs | −3% |
| `getObject`, 20 keys | 35.67 µs | 35.28 µs | −1% |
| `getValue(number, string, object)` | 13.94 µs | 14.21 µs | +2%, within noise |
| `voidFunc` | 4.55 µs | 4.59 µs | within noise |
| `getValueWithCallback` | 8.54 µs | 7.99 µs | −6.5% |
| `getValueWithPromise` | 18.26 µs | 17.38 µs | −5% |
Calls whose JNI signature is longer than the small-string buffer no longer allocate it, and are 5–6% cheaper. The cheapest calls, whose short signatures never allocated, now hash the method name instead of a pointer, about 50 ns more. Native library sizes are from the same two APKs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Differential Revision: D128349302
Pull Request resolved: #59619
Copy file name to clipboardExpand all lines: packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleJniCpp-test.js.snap
Copy file name to clipboardExpand all lines: packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaInteropTurboModule.cpp
Copy file name to clipboardExpand all lines: packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaInteropTurboModule.h
-1Lines changed: 0 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -40,7 +40,6 @@ class JSI_EXPORT JavaInteropTurboModule : public JavaTurboModule {
Copy file name to clipboardExpand all lines: packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
0 commit comments