Skip to content

Commit fec9d62

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. Changelog: [Internal] Differential Revision: D118277119
1 parent 1b40443 commit fec9d62

13 files changed

Lines changed: 97 additions & 87 deletions

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/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 & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
#include "ReadableNativeArray.h"
99

10-
#include "ReadableNativeMap.h"
11-
1210
using namespace facebook::jni;
1311

1412
namespace facebook::react {
@@ -23,7 +21,7 @@ void ReadableNativeArray::mapException(std::exception_ptr ex) {
2321
}
2422

2523
local_ref<JArrayClass<jobject>> ReadableNativeArray::importArray() {
26-
auto size = static_cast<jint>(array_.size());
24+
auto size = static_cast<jsize>(array_.size());
2725
auto jarray = JArrayClass<jobject>::newArray(size);
2826
for (jint ii = 0; ii < size; ii++) {
2927
addDynamicToJArray(jarray, ii, array_.at(ii));
@@ -32,10 +30,10 @@ local_ref<JArrayClass<jobject>> ReadableNativeArray::importArray() {
3230
}
3331

3432
local_ref<JArrayClass<jobject>> ReadableNativeArray::importTypeArray() {
35-
auto size = static_cast<jint>(array_.size());
33+
auto size = static_cast<jsize>(array_.size());
3634
auto jarray = JArrayClass<jobject>::newArray(size);
3735
for (jint ii = 0; ii < size; ii++) {
38-
(*jarray)[ii] = ReadableType::getType(array_.at(ii).type());
36+
jarray->setElement(ii, ReadableType::getType(array_.at(ii).type()).get());
3937
}
4038
return jarray;
4139
}

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: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,84 +20,86 @@ void ReadableNativeMap::mapException(std::exception_ptr ex) {
2020
}
2121
}
2222

23+
void ReadableNativeMap::throwIfKeysNotImported() const {
24+
if (!values_.has_value()) [[unlikely]] {
25+
throwNewJavaException(
26+
"java/lang/IllegalStateException",
27+
"importKeys must be called before importing values or types");
28+
}
29+
}
30+
2331
void addDynamicToJArray(
24-
local_ref<JArrayClass<jobject>> jarray,
32+
alias_ref<JArrayClass<jobject>> jarray,
2533
jint index,
2634
const folly::dynamic& dyn) {
35+
local_ref<jobject> value;
2736
switch (dyn.type()) {
28-
case folly::dynamic::Type::NULLT: {
29-
jarray->setElement(index, nullptr);
37+
case folly::dynamic::Type::BOOL:
38+
value = JBoolean::valueOf(static_cast<jboolean>(dyn.getBool()));
3039
break;
31-
}
32-
case folly::dynamic::Type::BOOL: {
33-
(*jarray)[index] =
34-
JBoolean::valueOf(static_cast<unsigned char>(dyn.getBool()));
40+
case folly::dynamic::Type::INT64:
41+
value = JDouble::valueOf(static_cast<double>(dyn.getInt()));
3542
break;
36-
}
37-
case folly::dynamic::Type::INT64: {
38-
(*jarray)[index] = JDouble::valueOf(dyn.getInt());
43+
case folly::dynamic::Type::DOUBLE:
44+
value = JDouble::valueOf(dyn.getDouble());
3945
break;
40-
}
41-
case folly::dynamic::Type::DOUBLE: {
42-
(*jarray)[index] = JDouble::valueOf(dyn.getDouble());
46+
case folly::dynamic::Type::STRING:
47+
value = make_jstring(dyn.getString());
4348
break;
44-
}
45-
case folly::dynamic::Type::STRING: {
46-
(*jarray)[index] = make_jstring(dyn.getString());
49+
case folly::dynamic::Type::OBJECT:
50+
value = ReadableNativeMap::newObjectCxxArgs(dyn);
4751
break;
48-
}
49-
case folly::dynamic::Type::OBJECT: {
50-
(*jarray)[index] = ReadableNativeMap::newObjectCxxArgs(dyn);
51-
break;
52-
}
53-
case folly::dynamic::Type::ARRAY: {
54-
(*jarray)[index] = ReadableNativeArray::newObjectCxxArgs(dyn);
52+
case folly::dynamic::Type::ARRAY:
53+
value = ReadableNativeArray::newObjectCxxArgs(dyn);
5554
break;
56-
}
55+
case folly::dynamic::Type::NULLT:
5756
default:
58-
jarray->setElement(index, nullptr);
5957
break;
6058
}
59+
jarray->setElement(index, value.get());
6160
}
6261

6362
local_ref<JArrayClass<jstring>> ReadableNativeMap::importKeys() {
6463
throwIfConsumed();
6564

66-
keys_ = folly::dynamic::array();
67-
if (map_ == nullptr) {
68-
return JArrayClass<jstring>::newArray(0);
69-
}
70-
auto jarray = JArrayClass<jstring>::newArray(map_.size());
65+
auto size = map_ == nullptr ? 0 : static_cast<jsize>(map_.size());
66+
std::vector<const folly::dynamic*> values(size);
67+
68+
auto jarray = JArrayClass<jstring>::newArray(size);
7169
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);
70+
if (map_ != nullptr) {
71+
for (auto& pair : map_.items()) {
72+
values[i] = &pair.second;
73+
jarray->setElement(i++, make_jstring(pair.first.getString()).get());
74+
}
7675
}
76+
values_ = std::move(values);
7777

7878
return jarray;
7979
}
8080

8181
local_ref<JArrayClass<jobject>> ReadableNativeMap::importValues() {
8282
throwIfConsumed();
83+
throwIfKeysNotImported();
8384

84-
auto size = static_cast<jint>(keys_.value().size());
85+
const auto& values = values_.value();
86+
auto size = static_cast<jsize>(values.size());
8587
auto jarray = JArrayClass<jobject>::newArray(size);
8688
for (jint ii = 0; ii < size; ii++) {
87-
const std::string& key = (*keys_)[ii].getString();
88-
addDynamicToJArray(jarray, ii, map_.at(key));
89+
addDynamicToJArray(jarray, ii, *values[ii]);
8990
}
9091
return jarray;
9192
}
9293

9394
local_ref<JArrayClass<jobject>> ReadableNativeMap::importTypes() {
9495
throwIfConsumed();
96+
throwIfKeysNotImported();
9597

96-
auto size = static_cast<jint>(keys_.value().size());
98+
const auto& values = values_.value();
99+
auto size = static_cast<jsize>(values.size());
97100
auto jarray = JArrayClass<jobject>::newArray(size);
98101
for (jint ii = 0; ii < size; ii++) {
99-
const std::string& key = (*keys_)[ii].getString();
100-
(*jarray)[ii] = ReadableType::getType(map_.at(key).type());
102+
jarray->setElement(ii, ReadableType::getType(values[ii]->type()).get());
101103
}
102104
return jarray;
103105
}

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

Lines changed: 8 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,12 @@ 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+
// Mutations are rejected once these borrowed pointers have been populated.
47+
std::optional<std::vector<const folly::dynamic *>> values_;
4448
};
4549

4650
} // namespace facebook::react

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,37 @@ void WritableNativeMap::initHybrid(alias_ref<jhybridobject> jobj) {
2424
setCxxInstance(jobj);
2525
}
2626

27-
void WritableNativeMap::putNull(std::string key) {
27+
void WritableNativeMap::throwIfNotWritable() {
2828
throwIfConsumed();
29+
if (values_.has_value()) [[unlikely]] {
30+
throwNewJavaException(
31+
"java/lang/IllegalStateException",
32+
"Cannot modify a map after its keys have been imported");
33+
}
34+
}
35+
36+
void WritableNativeMap::putNull(std::string key) {
37+
throwIfNotWritable();
2938
map_.insert(std::move(key), nullptr);
3039
}
3140

3241
void WritableNativeMap::putBoolean(std::string key, bool val) {
33-
throwIfConsumed();
42+
throwIfNotWritable();
3443
map_.insert(std::move(key), val);
3544
}
3645

3746
void WritableNativeMap::putDouble(std::string key, double val) {
38-
throwIfConsumed();
47+
throwIfNotWritable();
3948
map_.insert(std::move(key), val);
4049
}
4150

4251
void WritableNativeMap::putInt(std::string key, int val) {
43-
throwIfConsumed();
52+
throwIfNotWritable();
4453
map_.insert(std::move(key), val);
4554
}
4655

4756
void WritableNativeMap::putLong(std::string key, jlong val) {
48-
throwIfConsumed();
57+
throwIfNotWritable();
4958
map_.insert(std::move(key), val);
5059
}
5160

@@ -54,7 +63,7 @@ void WritableNativeMap::putString(std::string key, alias_ref<jstring> val) {
5463
putNull(std::move(key));
5564
return;
5665
}
57-
throwIfConsumed();
66+
throwIfNotWritable();
5867
map_.insert(std::move(key), val->toString());
5968
}
6069

@@ -65,7 +74,7 @@ void WritableNativeMap::putNativeArray(
6574
putNull(std::move(key));
6675
return;
6776
}
68-
throwIfConsumed();
77+
throwIfNotWritable();
6978
map_.insert(key, otherArray->consume());
7079
}
7180

@@ -76,12 +85,12 @@ void WritableNativeMap::putNativeMap(
7685
putNull(std::move(key));
7786
return;
7887
}
79-
throwIfConsumed();
88+
throwIfNotWritable();
8089
map_.insert(std::move(key), otherMap->consume());
8190
}
8291

8392
void WritableNativeMap::mergeNativeMap(ReadableNativeMap* other) {
84-
throwIfConsumed();
93+
throwIfNotWritable();
8594
other->throwIfConsumed();
8695

8796
for (const auto& sourceIt : other->map_.items()) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ struct WritableNativeMap : jni::HybridClass<WritableNativeMap, ReadableNativeMap
4141
static void registerNatives();
4242

4343
friend HybridBase;
44+
45+
private:
46+
void throwIfNotWritable();
4447
};
4548

4649
} // namespace facebook::react

0 commit comments

Comments
 (0)