Skip to content

Follow-up to #59414: cache each Java TurboModule method's ID with its signature, by name - #59619

Open
hryhoriiK97 wants to merge 3 commits into
react:mainfrom
hryhoriiK97:android-turbomodule-method-info-by-name
Open

hryhoriiK97 wants to merge 3 commits into
react:mainfrom
hryhoriiK97:android-turbomodule-method-info-by-name

Conversation

@hryhoriiK97

@hryhoriiK97 hryhoriiK97 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

This is a follow-up to #59414, suggested by @javache in its review: 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::strings; 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

JavaTurboModule now looks a method up on its first call and keeps its
jmethodID in the per-module MethodInfo cache with the parsed signature,
keyed by method name, instead of callers keeping the jmethodID themselves.
Generated host functions no longer hold a static jmethodID, and
JavaInteropTurboModule drops its methodIDs_ vector.

invokeJavaMethod takes the method name and signature as C strings, so
calls with literals don't build two std::strings each time. The overload
taking a cachedMethodID is kept for code generated by earlier codegen
versions and for modules that call it directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 29, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner labels Oct 29, 2026
// Kept for code generated by earlier versions of codegen and for modules
// that call it directly. cachedMethodID is neither read nor written: the
// method is cached by name.
jsi::Value invokeJavaMethod(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mark as deprecated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Oct 29, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hryhoriiK97
hryhoriiK97 requested a review from javache October 29, 2026 15:03
# Conflicts:
#	packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
Comment on lines +638 to +639
const char* methodName,
const char* methodSignature,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making these string_view instead?

@meta-codesync

meta-codesync Bot commented Oct 29, 2026

Copy link
Copy Markdown

@javache has imported this pull request. If you are a Meta employee, you can view this in D128349302.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants