Skip to content

Commit 52c576b

Browse files
shubh73meta-codesync[bot]
authored andcommitted
perf: Write executeMount int buffer to Java in a single JNI call (#58692)
Summary: `FabricMountingManager::executeMount` wrote the int buffer to Java with one `SetIntArrayRegion` per `writeInt`/`writeIntArray`, which is ~4.5k JNI calls for a commit that mounts 1500 views. The exact size is already known from `computeBufferSizes`, so this fills a `std::vector<jint>` instead and copies it once before `createIntBufferBatchMountItem`. The `int[]` Java receives is identical. `GetPrimitiveArrayCritical` isn't an option here since the object writes call into Java between int writes, and on ART `GetIntArrayElements` copies movable arrays anyway. Time spent in `executeMount` for one commit on an OPPO CPH2667 (Dimensity 6300, Android 16), release build. Each cell is the median over 114 commits from 6 interleaved runs: | Commit | JNI calls | Before | After | Change | |---|---|---|---|---| | Remove 1500 views | 3004 → 1 | 2.14 ms | 1.88 ms | −12% | | Add 1500 views | 4505 → 1 | 4.06 ms | 3.86 ms | −5% | | Restyle 1500 views (size, padding, color) | 4505 → 1 | 5.25 ms | 5.04 ms | −4% | The slowest commits improve more (e.g. 19.6 → 6.4 ms when restyling). The int transfer on its own goes from 174 → 87 µs per 1500-view commit. On an Apple silicon emulator JNI calls are ~5x cheaper and the difference is within noise. ## Changelog: [ANDROID] [CHANGED] - Write Fabric mount instruction ints to Java in a single JNI call Pull Request resolved: #58692 Test Plan: - Built a variant that also fills a shadow `int[]` the old way and compares it with the array sent to Java on every commit: 1390 commits on device and 782 on emulator across Playground (1500 views), FlatList, Text and Image, 0 mismatches. - RNTester (`yarn android` and release) on device and emulator: Playground, FlatList, Text and Image render as before. - `yarn lint`, `yarn flow-check`, `yarn format-check-cpp` pass. - Couldn't run `FabricMountingManagerTest` / `FabricMountingManagerInstrumentationTest`, they're Buck-only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: javache Differential Revision: D122059662 Pulled By: fabriziocucci fbshipit-source-id: 60c548755c14d3c07963586215ad53be635870d3
1 parent 208d445 commit 52c576b

1 file changed

Lines changed: 19 additions & 14 deletions

File tree

‎packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp‎

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "StateWrapperImpl.h"
1313

1414
#include <cxxreact/TraceSection.h>
15+
#include <react/debug/react_native_assert.h>
1516
#include <react/featureflags/ReactNativeFeatureFlags.h>
1617
#include <react/jni/ReadableNativeArray.h>
1718
#include <react/jni/ReadableNativeMap.h>
@@ -359,22 +360,23 @@ jni::local_ref<jobject> getProps(
359360
}
360361

361362
struct InstructionBuffer {
362-
JNIEnv* env;
363-
jintArray ints;
363+
InstructionBuffer(int intsSize, int objectsSize)
364+
: objects(jni::JArrayClass<jobject>::newArray(objectsSize)) {
365+
ints.reserve(intsSize);
366+
}
367+
368+
std::vector<jint> ints;
364369
jni::local_ref<jni::JArrayClass<jobject>> objects;
365370

366-
int intsPosition = 0;
367371
int objectsPosition = 0;
368372

369373
inline void writeInt(int value) {
370-
env->SetIntArrayRegion(ints, intsPosition, 1, &value);
371-
intsPosition += 1;
374+
ints.push_back(value);
372375
}
373376

374377
template <size_t N>
375378
inline void writeIntArray(const std::array<int, N>& buffer) {
376-
env->SetIntArrayRegion(ints, intsPosition, N, buffer.data());
377-
intsPosition += N;
379+
ints.insert(ints.end(), buffer.begin(), buffer.end());
378380
}
379381

380382
inline void writeObject(jobject obj) {
@@ -862,11 +864,7 @@ void FabricMountingManager::executeMount(
862864

863865
// Allocate the intBuffer and object array, now that we know exact sizes
864866
// necessary
865-
InstructionBuffer buffer = {
866-
.env = env,
867-
.ints = env->NewIntArray(batchMountItemIntsSize),
868-
.objects = jni::JArrayClass<jobject>::newArray(batchMountItemObjectsSize),
869-
};
867+
InstructionBuffer buffer(batchMountItemIntsSize, batchMountItemObjectsSize);
870868

871869
// Fill in arrays
872870
int prevMountItemType = -1;
@@ -996,6 +994,13 @@ void FabricMountingManager::executeMount(
996994
}
997995
}
998996

997+
// Copy the ints to Java in a single JNI call, rather than one per write
998+
react_native_assert(
999+
static_cast<int>(buffer.ints.size()) == batchMountItemIntsSize);
1000+
jintArray ints = env->NewIntArray(static_cast<jsize>(buffer.ints.size()));
1001+
env->SetIntArrayRegion(
1002+
ints, 0, static_cast<jsize>(buffer.ints.size()), buffer.ints.data());
1003+
9991004
static auto createMountItemsIntBufferBatchContainer =
10001005
JFabricUIManager::javaClassStatic()
10011006
->getMethod<jni::alias_ref<JMountItem>(
@@ -1006,7 +1011,7 @@ void FabricMountingManager::executeMount(
10061011
surfaceId,
10071012
// If there are no items, we pass a nullptr instead of passing the
10081013
// object through the JNI
1009-
batchMountItemIntsSize > 0 ? buffer.ints : nullptr,
1014+
batchMountItemIntsSize > 0 ? ints : nullptr,
10101015
batchMountItemObjectsSize > 0 ? buffer.objects.get() : nullptr,
10111016
revisionNumber);
10121017

@@ -1026,7 +1031,7 @@ void FabricMountingManager::executeMount(
10261031
telemetry.getAffectedLayoutNodesCount(),
10271032
static_cast<jboolean>(synchronous));
10281033

1029-
env->DeleteLocalRef(buffer.ints);
1034+
env->DeleteLocalRef(ints);
10301035
}
10311036

10321037
void FabricMountingManager::drainPreallocateViewsQueue() {

0 commit comments

Comments
 (0)