Files
react-native/ReactCommon/react/renderer/mounting/ShadowViewMutation.h
T
Joshua Gross b6bbbf8efa RemoveDeleteTree mount instruction
Summary:
TL;DR: For applications using JS navigation, save 50-95% of CPU during mounting phase in N>2 navigations that replace ~most of screen.

During investigation of performance on the UI thread of React Native applications, I noticed that the /initial/ render of an screen for an application using JS navigation is /mostly/ consumed (on the UI thread) by tearing-down the previous View hierarchy. In one 185ms segment on the UI thread in production, 95% of the CPU time was Remove/Delete instructions and only 5% of CPU time was consumed by actually displaying the new hierarchy (this is specific to Android and also assumes that View Preallocation is being used, so post-commit work consists of Insert and UpdateLayout mutations primarily).

There are /some/ cases where the C++ differ knows that we are deleting an entire subtree and therefore we could communicate this to the mounting layer. All that matters is that these Views are removed from the View hierarchy immediately; and secondarily that their memory is cleaned up ASAP, but that doesn't need to happen immediately.

Some additional constraints and notes:

1) As noted in the comments, we cannot simply stop producing Remove and Delete instructions. We need to produce /both/ the new RemoveDeleteTree instruction, /and/ produce all the Remove/Delete instructions, primarily because LayoutAnimations relies heavily on these Remove/Delete instructions and certain things would break if we removed those instructions entirely. However, we can mark those Remove/Delete instructions as redundant, process them only in LayoutAnimations, and not send them to the Android mounting layer.
2) We want to make sure that View Recycling is not impacted. Since Android cannot take advantage of View Recycling until /after/ the second major render (preallocation of views will happen before any views are recycled), this doesn't impact View Recycling and we'll make sure Views are recycled whenever they are deleted.

Thus, we do two things:

1) Introduce a new RemoveDeleteTree operation that can delete an entire subtree recursively as part of one operation. This allows us to avoid serializing hundreds or thousands of instructions and prevents JNI traffic.
2) Besides removing the topmost View from the View hierarchy, and ensuring it's not drawn, the full teardown and recycling of the tree can happen /after/ the paint.

In some flows with JS navigation this saves us 95% of CPU during the mount phase. In the general case it is probably closer to 25-50% of CPU time that is saved and/or deferred.

Changelog: [Android][Changed] Significant perf optimization to Fabric Remove/Delete operations

Reviewed By: ryancat

Differential Revision: D37257864

fbshipit-source-id: a7d33fc74683939965cfb98be4db7890644110b2
2022-06-25 16:41:23 -07:00

136 lines
3.6 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <vector>
#include <react/renderer/mounting/ShadowView.h>
namespace facebook {
namespace react {
/*
* Describes a single native view tree mutation which may contain
* pointers to an old shadow view, a new shadow view, a parent shadow view and
* final index of inserted or updated view.
* Use static methods to instantiate mutations of different types.
*/
struct ShadowViewMutation final {
using List = std::vector<ShadowViewMutation>;
ShadowViewMutation() = delete;
#pragma mark - Platform feature flags
static bool PlatformSupportsRemoveDeleteTreeInstruction;
#pragma mark - Designated Initializers
/*
* Creates and returns an `Create` mutation.
*/
static ShadowViewMutation CreateMutation(ShadowView shadowView);
/*
* Creates and returns an `Delete` mutation.
*/
static ShadowViewMutation DeleteMutation(
ShadowView shadowView,
bool isRedundantOperation = false);
/*
* Creates and returns an `Insert` mutation.
*/
static ShadowViewMutation InsertMutation(
ShadowView parentShadowView,
ShadowView childShadowView,
int index);
/*
* Creates and returns a `Remove` mutation.
*/
static ShadowViewMutation RemoveMutation(
ShadowView parentShadowView,
ShadowView childShadowView,
int index,
bool isRedundantOperation = false);
/*
* Creates and returns a `RemoveDelete` mutation.
* This is a signal to (for supported platforms)
* remove and delete an entire subtree with a single
* instruction.
*/
static ShadowViewMutation RemoveDeleteTreeMutation(
ShadowView parentShadowView,
ShadowView childShadowView,
int index);
/*
* Creates and returns an `Update` mutation.
*/
static ShadowViewMutation UpdateMutation(
ShadowView oldChildShadowView,
ShadowView newChildShadowView);
#pragma mark - Type
enum Type {
Create = 1,
Delete = 2,
Insert = 4,
Remove = 8,
Update = 16,
RemoveDeleteTree = 32
};
#pragma mark - Fields
Type type = {Create};
ShadowView parentShadowView = {};
ShadowView oldChildShadowView = {};
ShadowView newChildShadowView = {};
int index = -1;
// RemoveDeleteTree causes many Remove/Delete operations to be redundant.
// However, we must internally produce all of them for any consumers that
// rely on explicit instructions to remove/delete every node in the tree.
// Notably (as of the time of writing this) LayoutAnimations.
bool isRedundantOperation = false;
// Some platforms can have the notion of virtual views - views that are in the
// ShadowTree hierarchy but never are on the platform. Generally this is used
// so notify the platform that a view exists so that we can keep EventEmitters
// around, to notify JS of something. This mechanism is DEPRECATED and it is
// highly recommended that you NOT make use of this in your platform!
bool mutatedViewIsVirtual() const;
private:
ShadowViewMutation(
Type type,
ShadowView parentShadowView,
ShadowView oldChildShadowView,
ShadowView newChildShadowView,
int index,
bool isRedundantOperation = false);
};
using ShadowViewMutationList = std::vector<ShadowViewMutation>;
#if RN_DEBUG_STRING_CONVERTIBLE
std::string getDebugName(ShadowViewMutation const &object);
std::vector<DebugStringConvertibleObject> getDebugProps(
ShadowViewMutation const &object,
DebugStringConvertibleOptions options);
#endif
} // namespace react
} // namespace facebook