From 01523ae6604c3b1912e62fc7281abd92aed6c32c Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Fri, 3 May 2019 12:27:24 -0700 Subject: [PATCH] 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 --- .../fabric/mounting/Differentiator.cpp | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/ReactCommon/fabric/mounting/Differentiator.cpp b/ReactCommon/fabric/mounting/Differentiator.cpp index 2c7e997b3..84d4931c2 100644 --- a/ReactCommon/fabric/mounting/Differentiator.cpp +++ b/ReactCommon/fabric/mounting/Differentiator.cpp @@ -6,6 +6,7 @@ #include "Differentiator.h" #include +#include #include #include #include "ShadowView.h" @@ -13,6 +14,69 @@ namespace facebook { namespace react { +/* + * Extremely simple and naive implementation of a map. + * The map is simple but it's optimized for particular constraints that we have + * here. + * + * A regular map implementation (e.g. `std::unordered_map`) has some basic + * performance guarantees like constant average insertion and lookup complexity. + * This is nice, but it's *average* complexity measured on a non-trivial amount + * of data. The regular map is a very complex data structure that using hashing, + * buckets, multiple comprising operations, multiple allocations and so on. + * + * In our particular case, we need a map for `int` to `void *` with a dozen + * values. In these conditions, nothing can beat a naive implementation using a + * stack-allocated vector. And this implementation is exactly this: no + * allocation, no hashing, no complex branching, no buckets, no iterators, no + * rehashing, no other guarantees. It's crazy limited, unsafe, and performant on + * a trivial amount of data. + * + * Besides that, we also need to optimize for insertion performance (the case + * where a bunch of views appears on the screen first time); in this + * implementation, this is as performant as vector `push_back`. + */ +template +class TinyMap final { + public: + using Pair = std::pair; + using Iterator = Pair *; + + inline Iterator begin() { + return (Pair *)vector_; + } + + inline Iterator end() { + return nullptr; + } + + inline Iterator find(KeyT key) { + for (auto &item : vector_) { + if (item.first == key) { + return &item; + } + } + + return end(); + } + + inline void insert(Pair pair) { + assert(pair.first != 0); + vector_.push_back(pair); + } + + inline void erase(Iterator iterator) { + static_assert( + std::is_same::value, + "The collection is designed to store only `Tag`s as keys."); + // Zero is a invalid tag. + iterator->first = 0; + } + + private: + better::small_vector vector_; +}; + static void sliceChildShadowNodeViewPairsRecursively( ShadowViewNodePair::List &pairList, Point layoutOffset, @@ -70,7 +134,7 @@ static void calculateShadowViewMutations( auto index = int{0}; // Maps inserted node tags to pointers to them in `newChildPairs`. - auto insertedPairs = better::map{}; + auto insertedPairs = TinyMap{}; // Lists of mutations auto createMutations = ShadowViewMutation::List{};