Skip to content

Commit 7deec41

Browse files
javachefacebook-github-bot
authored andcommitted
Reduce JNI allocations when importing native maps (#58274)
Summary: `ReadableNativeMap` materialization copied keys and created temporary JNI references for every imported type. Cache pointers to the stable native values and reuse global `ReadableType` references so importing maps and arrays does less allocation and lookup work. Writable maps can continue mutating after materialization because `folly::dynamic` stores object entries in reference-stable `F14NodeMap` nodes. Changelog: [Internal] Reviewed By: christophpurrer, rubennorte Differential Revision: D118277119
1 parent d9ad3f0 commit 7deec41

15 files changed

Lines changed: 101 additions & 77 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <cxxreact/TraceSection.h>
1515
#include <react/featureflags/ReactNativeFeatureFlags.h>
16+
#include <react/jni/ReadableNativeArray.h>
1617
#include <react/jni/ReadableNativeMap.h>
1718
#include <react/renderer/components/scrollview/ScrollViewProps.h>
1819
#include <react/renderer/core/DynamicPropsUtilities.h>

packages/react-native/ReactAndroid/src/main/jni/react/jni/JDynamicNative.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jboolean JDynamicNative::isNullNative() {
1717
return static_cast<jboolean>(payload_.isNull());
1818
}
1919

20-
jni::local_ref<ReadableType> JDynamicNative::getTypeNative() {
20+
jni::alias_ref<ReadableType> JDynamicNative::getTypeNative() {
2121
return ReadableType::getType(payload_.type());
2222
}
2323

packages/react-native/ReactAndroid/src/main/jni/react/jni/JDynamicNative.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class JDynamicNative : public jni::HybridClass<JDynamicNative, JDynamic> {
4242
private:
4343
friend HybridBase;
4444

45-
jni::local_ref<ReadableType> getTypeNative();
45+
jni::alias_ref<ReadableType> getTypeNative();
4646
jni::local_ref<jstring> asString();
4747
jboolean asBoolean();
4848
jdouble asDouble();

packages/react-native/ReactAndroid/src/main/jni/react/jni/JavaModuleWrapper.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <fbsystrace.h>
2121
#endif
2222

23+
#include "NativeMap.h"
2324
#include "ReadableNativeArray.h"
2425

2526
#ifndef RCT_REMOVE_LEGACY_ARCH

packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeCommon.cpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,32 @@ alias_ref<ReadableType> getTypeField(const char* fieldName) {
2727

2828
} // namespace
2929

30-
local_ref<ReadableType> ReadableType::getType(folly::dynamic::Type type) {
30+
alias_ref<ReadableType> ReadableType::getType(folly::dynamic::Type type) {
3131
switch (type) {
3232
case folly::dynamic::Type::NULLT: {
33-
static alias_ref<ReadableType> val = getTypeField("Null");
34-
return make_local(val);
33+
static auto val = getTypeField("Null");
34+
return val;
3535
}
3636
case folly::dynamic::Type::BOOL: {
37-
static alias_ref<ReadableType> val = getTypeField("Boolean");
38-
return make_local(val);
37+
static auto val = getTypeField("Boolean");
38+
return val;
3939
}
4040
case folly::dynamic::Type::DOUBLE:
4141
case folly::dynamic::Type::INT64: {
42-
static alias_ref<ReadableType> val = getTypeField("Number");
43-
return make_local(val);
42+
static auto val = getTypeField("Number");
43+
return val;
4444
}
4545
case folly::dynamic::Type::STRING: {
46-
static alias_ref<ReadableType> val = getTypeField("String");
47-
return make_local(val);
46+
static auto val = getTypeField("String");
47+
return val;
4848
}
4949
case folly::dynamic::Type::OBJECT: {
50-
static alias_ref<ReadableType> val = getTypeField("Map");
51-
return make_local(val);
50+
static auto val = getTypeField("Map");
51+
return val;
5252
}
5353
case folly::dynamic::Type::ARRAY: {
54-
static alias_ref<ReadableType> val = getTypeField("Array");
55-
return make_local(val);
54+
static auto val = getTypeField("Array");
55+
return val;
5656
}
5757
default:
5858
throwNewJavaException(

packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeCommon.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace facebook::react {
1919
struct ReadableType : public jni::JavaClass<ReadableType> {
2020
static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/ReadableType;";
2121

22-
static jni::local_ref<ReadableType> getType(folly::dynamic::Type type);
22+
static jni::alias_ref<ReadableType> getType(folly::dynamic::Type type);
2323
};
2424

2525
namespace exceptions {
@@ -29,7 +29,7 @@ extern const char *gUnexpectedNativeTypeExceptionClass;
2929
template <typename T>
3030
void throwIfObjectAlreadyConsumed(const T &t, const char *msg)
3131
{
32-
if (t->isConsumed) {
32+
if (t->isConsumed) [[unlikely]] {
3333
jni::throwNewJavaException("com/facebook/react/bridge/ObjectAlreadyConsumedException", msg);
3434
}
3535
}

packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ void ReadableNativeArray::mapException(std::exception_ptr ex) {
2323
}
2424

2525
local_ref<JArrayClass<jobject>> ReadableNativeArray::importArray() {
26-
auto size = static_cast<jint>(array_.size());
26+
auto size = static_cast<jsize>(array_.size());
2727
auto jarray = JArrayClass<jobject>::newArray(size);
2828
for (jint ii = 0; ii < size; ii++) {
2929
addDynamicToJArray(jarray, ii, array_.at(ii));
@@ -32,10 +32,10 @@ local_ref<JArrayClass<jobject>> ReadableNativeArray::importArray() {
3232
}
3333

3434
local_ref<JArrayClass<jobject>> ReadableNativeArray::importTypeArray() {
35-
auto size = static_cast<jint>(array_.size());
35+
auto size = static_cast<jsize>(array_.size());
3636
auto jarray = JArrayClass<jobject>::newArray(size);
3737
for (jint ii = 0; ii < size; ii++) {
38-
(*jarray)[ii] = ReadableType::getType(array_.at(ii).type());
38+
jarray->setElement(ii, ReadableType::getType(array_.at(ii).type()).get());
3939
}
4040
return jarray;
4141
}

packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99

1010
#include "NativeArray.h"
1111

12-
#include "NativeCommon.h"
13-
#include "NativeMap.h"
14-
1512
namespace facebook::react {
1613

1714
struct ReadableArray : jni::JavaClass<ReadableArray> {

packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.cpp

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "ReadableNativeMap.h"
99

10+
#include "ReadableNativeArray.h"
11+
1012
using namespace facebook::jni;
1113

1214
namespace facebook::react {
@@ -20,84 +22,86 @@ void ReadableNativeMap::mapException(std::exception_ptr ex) {
2022
}
2123
}
2224

25+
void ReadableNativeMap::throwIfKeysNotImported() const {
26+
if (!values_.has_value()) [[unlikely]] {
27+
throwNewJavaException(
28+
"java/lang/IllegalStateException",
29+
"importKeys must be called before importing values or types");
30+
}
31+
}
32+
2333
void addDynamicToJArray(
24-
local_ref<JArrayClass<jobject>> jarray,
34+
alias_ref<JArrayClass<jobject>> jarray,
2535
jint index,
2636
const folly::dynamic& dyn) {
37+
local_ref<jobject> value;
2738
switch (dyn.type()) {
28-
case folly::dynamic::Type::NULLT: {
29-
jarray->setElement(index, nullptr);
30-
break;
31-
}
32-
case folly::dynamic::Type::BOOL: {
33-
(*jarray)[index] =
34-
JBoolean::valueOf(static_cast<unsigned char>(dyn.getBool()));
39+
case folly::dynamic::Type::BOOL:
40+
value = JBoolean::valueOf(static_cast<jboolean>(dyn.getBool()));
3541
break;
36-
}
37-
case folly::dynamic::Type::INT64: {
38-
(*jarray)[index] = JDouble::valueOf(dyn.getInt());
42+
case folly::dynamic::Type::INT64:
43+
value = JDouble::valueOf(static_cast<double>(dyn.getInt()));
3944
break;
40-
}
41-
case folly::dynamic::Type::DOUBLE: {
42-
(*jarray)[index] = JDouble::valueOf(dyn.getDouble());
45+
case folly::dynamic::Type::DOUBLE:
46+
value = JDouble::valueOf(dyn.getDouble());
4347
break;
44-
}
45-
case folly::dynamic::Type::STRING: {
46-
(*jarray)[index] = make_jstring(dyn.getString());
48+
case folly::dynamic::Type::STRING:
49+
value = make_jstring(dyn.getString());
4750
break;
48-
}
49-
case folly::dynamic::Type::OBJECT: {
50-
(*jarray)[index] = ReadableNativeMap::newObjectCxxArgs(dyn);
51+
case folly::dynamic::Type::OBJECT:
52+
value = ReadableNativeMap::newObjectCxxArgs(dyn);
5153
break;
52-
}
53-
case folly::dynamic::Type::ARRAY: {
54-
(*jarray)[index] = ReadableNativeArray::newObjectCxxArgs(dyn);
54+
case folly::dynamic::Type::ARRAY:
55+
value = ReadableNativeArray::newObjectCxxArgs(dyn);
5556
break;
56-
}
57+
case folly::dynamic::Type::NULLT:
5758
default:
58-
jarray->setElement(index, nullptr);
5959
break;
6060
}
61+
jarray->setElement(index, value.get());
6162
}
6263

6364
local_ref<JArrayClass<jstring>> ReadableNativeMap::importKeys() {
6465
throwIfConsumed();
6566

66-
keys_ = folly::dynamic::array();
67-
if (map_ == nullptr) {
68-
return JArrayClass<jstring>::newArray(0);
69-
}
70-
auto jarray = JArrayClass<jstring>::newArray(map_.size());
67+
auto size = map_ == nullptr ? 0 : static_cast<jsize>(map_.size());
68+
std::vector<const folly::dynamic*> values(size);
69+
70+
auto jarray = JArrayClass<jstring>::newArray(size);
7171
jint i = 0;
72-
for (auto& pair : map_.items()) {
73-
auto value = pair.first.asString();
74-
(*keys_).push_back(value);
75-
(*jarray)[i++] = make_jstring(value);
72+
if (map_ != nullptr) {
73+
for (auto& pair : map_.items()) {
74+
values[i] = &pair.second;
75+
jarray->setElement(i++, make_jstring(pair.first.getString()).get());
76+
}
7677
}
78+
values_ = std::move(values);
7779

7880
return jarray;
7981
}
8082

8183
local_ref<JArrayClass<jobject>> ReadableNativeMap::importValues() {
8284
throwIfConsumed();
85+
throwIfKeysNotImported();
8386

84-
auto size = static_cast<jint>(keys_.value().size());
87+
const auto& values = values_.value();
88+
auto size = static_cast<jsize>(values.size());
8589
auto jarray = JArrayClass<jobject>::newArray(size);
8690
for (jint ii = 0; ii < size; ii++) {
87-
const std::string& key = (*keys_)[ii].getString();
88-
addDynamicToJArray(jarray, ii, map_.at(key));
91+
addDynamicToJArray(jarray, ii, *values[ii]);
8992
}
9093
return jarray;
9194
}
9295

9396
local_ref<JArrayClass<jobject>> ReadableNativeMap::importTypes() {
9497
throwIfConsumed();
98+
throwIfKeysNotImported();
9599

96-
auto size = static_cast<jint>(keys_.value().size());
100+
const auto& values = values_.value();
101+
auto size = static_cast<jsize>(values.size());
97102
auto jarray = JArrayClass<jobject>::newArray(size);
98103
for (jint ii = 0; ii < size; ii++) {
99-
const std::string& key = (*keys_)[ii].getString();
100-
(*jarray)[ii] = ReadableType::getType(map_.at(key).type());
104+
jarray->setElement(ii, ReadableType::getType(values[ii]->type()).get());
101105
}
102106
return jarray;
103107
}

packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeMap.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@
1111
#include <folly/dynamic.h>
1212
#include <folly/json.h>
1313
#include <optional>
14+
#include <vector>
1415

15-
#include "NativeCommon.h"
1616
#include "NativeMap.h"
17-
#include "ReadableNativeArray.h"
1817

1918
namespace facebook::react {
2019

@@ -24,15 +23,14 @@ struct ReadableMap : jni::JavaClass<ReadableMap> {
2423
static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/ReadableMap;";
2524
};
2625

27-
void addDynamicToJArray(jni::local_ref<jni::JArrayClass<jobject>> jarray, jint index, const folly::dynamic &dyn);
26+
void addDynamicToJArray(jni::alias_ref<jni::JArrayClass<jobject>> jarray, jint index, const folly::dynamic &dyn);
2827

2928
struct ReadableNativeMap : jni::HybridClass<ReadableNativeMap, NativeMap> {
3029
static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/ReadableNativeMap;";
3130

3231
jni::local_ref<jni::JArrayClass<jstring>> importKeys();
3332
jni::local_ref<jni::JArrayClass<jobject>> importValues();
3433
jni::local_ref<jni::JArrayClass<jobject>> importTypes();
35-
std::optional<folly::dynamic> keys_;
3634
static jni::local_ref<jhybridobject> createWithContents(folly::dynamic &&map);
3735

3836
static void mapException(std::exception_ptr ex);
@@ -41,6 +39,14 @@ struct ReadableNativeMap : jni::HybridClass<ReadableNativeMap, NativeMap> {
4139
using HybridBase::HybridBase;
4240
friend HybridBase;
4341
friend struct WritableNativeMap;
42+
43+
private:
44+
void throwIfKeysNotImported() const;
45+
46+
// folly::dynamic stores object entries in an F14NodeMap, so these pointers
47+
// remain valid across the insertions and replacements exposed by
48+
// WritableNativeMap.
49+
std::optional<std::vector<const folly::dynamic *>> values_;
4450
};
4551

4652
} // namespace facebook::react

0 commit comments

Comments
 (0)