Skip to content

Commit 01523ae

Browse files
sherginfacebook-github-bot
authored andcommitted
Fabric: TinyMap in Differentiator
Summary: Diffing algorithm uses a small map for every layer of shadow tree; that's a lot of maps. Luckily, it does not need a complex feature-full map (which is not free to allocate and use), it needs a tiny map with a dozen values. Why do we need to pay for what we don't use? This diff introduces a trivial map optimized for constraints that we have here. (See more details in the code.) Reviewed By: mdvacca Differential Revision: D15200495 fbshipit-source-id: d859b68b9543253840b403e7430f945a0b76d87b
1 parent c5f0969 commit 01523ae

File tree

1 file changed

+65
-1
lines changed

1 file changed

+65
-1
lines changed

ReactCommon/fabric/mounting/Differentiator.cpp

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,77 @@
66
#include "Differentiator.h"
77

88
#include <better/map.h>
9+
#include <better/small_vector.h>
910
#include <react/core/LayoutableShadowNode.h>
1011
#include <react/debug/SystraceSection.h>
1112
#include "ShadowView.h"
1213

1314
namespace facebook {
1415
namespace react {
1516

17+
/*
18+
* Extremely simple and naive implementation of a map.
19+
* The map is simple but it's optimized for particular constraints that we have
20+
* here.
21+
*
22+
* A regular map implementation (e.g. `std::unordered_map`) has some basic
23+
* performance guarantees like constant average insertion and lookup complexity.
24+
* This is nice, but it's *average* complexity measured on a non-trivial amount
25+
* of data. The regular map is a very complex data structure that using hashing,
26+
* buckets, multiple comprising operations, multiple allocations and so on.
27+
*
28+
* In our particular case, we need a map for `int` to `void *` with a dozen
29+
* values. In these conditions, nothing can beat a naive implementation using a
30+
* stack-allocated vector. And this implementation is exactly this: no
31+
* allocation, no hashing, no complex branching, no buckets, no iterators, no
32+
* rehashing, no other guarantees. It's crazy limited, unsafe, and performant on
33+
* a trivial amount of data.
34+
*
35+
* Besides that, we also need to optimize for insertion performance (the case
36+
* where a bunch of views appears on the screen first time); in this
37+
* implementation, this is as performant as vector `push_back`.
38+
*/
39+
template <typename KeyT, typename ValueT, int DefaultSize = 16>
40+
class TinyMap final {
41+
public:
42+
using Pair = std::pair<KeyT, ValueT>;
43+
using Iterator = Pair *;
44+
45+
inline Iterator begin() {
46+
return (Pair *)vector_;
47+
}
48+
49+
inline Iterator end() {
50+
return nullptr;
51+
}
52+
53+
inline Iterator find(KeyT key) {
54+
for (auto &item : vector_) {
55+
if (item.first == key) {
56+
return &item;
57+
}
58+
}
59+
60+
return end();
61+
}
62+
63+
inline void insert(Pair pair) {
64+
assert(pair.first != 0);
65+
vector_.push_back(pair);
66+
}
67+
68+
inline void erase(Iterator iterator) {
69+
static_assert(
70+
std::is_same<KeyT, Tag>::value,
71+
"The collection is designed to store only `Tag`s as keys.");
72+
// Zero is a invalid tag.
73+
iterator->first = 0;
74+
}
75+
76+
private:
77+
better::small_vector<Pair, DefaultSize> vector_;
78+
};
79+
1680
static void sliceChildShadowNodeViewPairsRecursively(
1781
ShadowViewNodePair::List &pairList,
1882
Point layoutOffset,
@@ -70,7 +134,7 @@ static void calculateShadowViewMutations(
70134
auto index = int{0};
71135

72136
// Maps inserted node tags to pointers to them in `newChildPairs`.
73-
auto insertedPairs = better::map<Tag, ShadowViewNodePair const *>{};
137+
auto insertedPairs = TinyMap<Tag, ShadowViewNodePair const *>{};
74138

75139
// Lists of mutations
76140
auto createMutations = ShadowViewMutation::List{};

0 commit comments

Comments
 (0)