Repository navigation
Commit f7de203
authored
Parse each Java TurboModule method's JNI signature once
## Summary:
This is the Android counterpart of #59394.
On Android, every call from JS to a Java TurboModule method goes through `JavaTurboModule::invokeJavaMethod` (`ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp`). On every call it parsed the method's JNI signature into a new `std::vector<std::string>` of argument types (`getMethodArgTypesFromSignature`), and for methods that return a number or a boolean it cut the return type out of the signature again with `substr`. Long type names such as `Lcom/facebook/react/bridge/ReadableMap;` don't fit in the small-string buffer, so each of them was a heap allocation on every call.
A method's signature doesn't change, so this change parses it on the method's first call and keeps the result. Each `JavaTurboModule` now has one `MethodInfo` per method (the argument types and the return type), keyed by the method's `jmethodID`, which is already resolved at that point. A mutex guards the map. Entries are never removed while the module is alive, and `std::unordered_map` doesn't move its elements, so the returned reference stays valid for the rest of the call. The members are private, so there is no API change.
Memory: one entry per method that is actually called, about 150–300 bytes each (the type strings and the map node). The same strings were already allocated on every call and freed right after.
One side effect: `JavaTurboModule` now holds a `std::mutex`, so it can't be copied or moved. Copying a module was already deprecated (it has a user-declared destructor) and nothing in the repository copies one; generated modules are created with `std::make_shared`.
## Changelog:
[ANDROID] [CHANGED] - Parse each TurboModule method's JNI signature once instead of on every call
## Test Plan:
- `clang-format --dry-run --Werror` (the repository's pinned version) on the changed files passes.
- RNTester built with Gradle in Release (`assembleRelease`, arm64-v8a) and run on a physical Samsung Galaxy A14 (Android 15). In the TurboModule example, "Run function call tests" returns the same values for all 18 methods with and without this change, including callbacks, a resolved and a rejected promise, and ArrayBuffers.
- To run `SampleTurboModule` in a Release build I had to change two things locally, in the sample module only and in both builds:
- In Release, creating it fails with `A launcher is already registered for key 'com.facebook.fbreact.specs.SampleTurboModule:e2'`. The launcher key is built from the contract class names, and R8 seems to merge the contract classes, so the three launchers get the same key. I passed an explicit key to each `registerForActivityResult` call.
- It shows a `Toast` on every call, so I turned that off for the measurement.
- **Measurement.** RNTester Playground calling `NativeSampleTurboModule` methods in a loop (20,000 calls each for the sync methods, 5,000 for the callback), timed with `performance.now()`. Two Release APKs, before and after this change, installed in the order before, after, after, before, 4 launches each after a warm-up launch. The presses were driven by [Argent](https://github.com/software-mansion/argent) on the physical phone. Mean time per call:
| Call | before | this PR | change |
|---|--:|--:|--:|
| `getString` | 1.93 µs | 1.66 µs | −14% |
| `getValue` (number, string, object) | 18.96 µs | 17.62 µs | −7% |
| `getValueWithCallback` | 11.46 µs | 10.66 µs | −7% |
| `getArray` (3 items) | 15.84 µs | 14.83 µs | −6% |
| `getObject` (small) | 14.02 µs | 13.19 µs | −6% |
For these rows, the ranges of the two arms don't overlap. `getNumber` (1.28 → 1.14 µs), `getBool`, the 20-key `getObject`, `voidFunc` and promises were within noise in this run. An earlier run of the same change behind a switch in one APK gave −5% to −20% for these calls and `getNumber` (−20%).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Differential Revision: D128040903
Pull Request resolved: #594141 parent 745b4f3 commit f7de203
2 files changed
Lines changed: 34 additions & 7 deletions
File tree
- packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
588 | 588 | | |
589 | 589 | | |
590 | 590 | | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
591 | 609 | | |
592 | 610 | | |
593 | 611 | | |
| |||
689 | 707 | | |
690 | 708 | | |
691 | 709 | | |
692 | | - | |
693 | | - | |
| 710 | + | |
694 | 711 | | |
695 | 712 | | |
696 | 713 | | |
697 | 714 | | |
698 | 715 | | |
699 | | - | |
| 716 | + | |
700 | 717 | | |
701 | 718 | | |
702 | 719 | | |
| |||
712 | 729 | | |
713 | 730 | | |
714 | 731 | | |
715 | | - | |
716 | | - | |
| 732 | + | |
717 | 733 | | |
718 | 734 | | |
719 | 735 | | |
| |||
749 | 765 | | |
750 | 766 | | |
751 | 767 | | |
752 | | - | |
753 | | - | |
| 768 | + | |
754 | 769 | | |
755 | 770 | | |
756 | 771 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
| 14 | + | |
| 15 | + | |
13 | 16 | | |
14 | 17 | | |
15 | 18 | | |
| |||
61 | 64 | | |
62 | 65 | | |
63 | 66 | | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
64 | 76 | | |
65 | 77 | | |
66 | 78 | | |
0 commit comments