mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-01-12 22:50:10 +08:00
Fabric: Simpler view preallocation on iOS
Summary: I measured the performance of UIMananger methods and found that `create` method spends a significant amount of time on preallocating views (around 10 ms on my device). Interestingly, this time was actually spent on dispatching operations on the main thread, not on performing those operations. That makes me think about think about the preallocation problem one more time. (Here and later I discuss preemptive optimistic preallocation views on iOS, but the issues are more-less applicable for all platforms.) BTW, what's view preallocation? – In React Native we believe that we can get significant TTI wins instantiating platform views ahead of time while JavaScript thread is busy doing reconciliation and the main one is idle. So, seems the current approach has those downsides: * We spend too much time on dispatching only (jumping threads, creation Objective-C blocks, managing them in GCD, incrementing atomic refcountes inside `ShadowView` and so on). That's wasteful. * We don't have much information during preallocations that can help prepare something to save time in the future. We don't know the future size of the component, so we cannot e.g. pre-draw a border or even allocate memory for a future drawing. * We pre-allocate to much. At the moment of component creation, we don't know will this component be filtered out by view-flattening infra or not, so we always allocate a view for it. That's ~30% more views that we actually need. * We don't stop allocating (or dispatching instructions for that) after the recycle pool is already filled in. That's also wasteful. Why is this so bad and how can we fix it properly?.. I thought about this problem for months. And I think the answer is trivial: We don't know the exact shape of the interface until we finish creating it, and there is no way to overcome this problem on the client side. In the ideal world, the server size (or hardcoded manifest somewhere) tells the renderer (at the moment where we started navigating to it) how many views of which type will some surface use. And until our world is not so perfect, I think we can get a significant performance improvement doing simple preallocation of 100 regular views, 30 text views, and 30 image views during rendering the first React Native surface. So I hardcoded that. Reviewed By: JoshuaGross Differential Revision: D14333606 fbshipit-source-id: 96beeb58b546258de1b8fd58550e0ae412b78aa9
This commit is contained in:
committed by
Facebook Github Bot
parent
e3b2818581
commit
205171cab0
@@ -10,6 +10,10 @@
|
||||
#import <Foundation/NSMapTable.h>
|
||||
#import <React/RCTAssert.h>
|
||||
|
||||
#import "RCTImageComponentView.h"
|
||||
#import "RCTParagraphComponentView.h"
|
||||
#import "RCTViewComponentView.h"
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
#define LEGACY_UIMANAGER_INTEGRATION_ENABLED 1
|
||||
@@ -87,11 +91,38 @@ const NSInteger RCTComponentViewRegistryRecyclePoolMaxSize = 1024;
|
||||
selector:@selector(handleApplicationDidReceiveMemoryWarningNotification)
|
||||
name:UIApplicationDidReceiveMemoryWarningNotification
|
||||
object:nil];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
// Calling this a bit later, when the main thread is probably idle while JavaScript thread is busy.
|
||||
[self preallocateViewComponents];
|
||||
});
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)preallocateViewComponents
|
||||
{
|
||||
// This data is based on empirical evidence which should represent the reality pretty well.
|
||||
// Regular `<View>` has magnitude equals to `1` by definition.
|
||||
std::vector<std::pair<ComponentHandle, float>> componentMagnitudes = {
|
||||
{[RCTViewComponentView componentHandle], 1},
|
||||
{[RCTImageComponentView componentHandle], 0.3},
|
||||
{[RCTParagraphComponentView componentHandle], 0.3},
|
||||
};
|
||||
|
||||
// `complexity` represents the complexity of a typical surface in a number of `<View>` components (with Flattening
|
||||
// enabled).
|
||||
float complexity = 100;
|
||||
|
||||
// The whole process should not take more than 10ms in the worst case, so there is no need to split it up.
|
||||
for (const auto &componentMagnitude : componentMagnitudes) {
|
||||
for (int i = 0; i < complexity * componentMagnitude.second; i++) {
|
||||
[self optimisticallyCreateComponentViewWithComponentHandle:componentMagnitude.first];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
@@ -35,13 +35,8 @@ class SchedulerDelegateProxy : public SchedulerDelegate {
|
||||
|
||||
void schedulerDidRequestPreliminaryViewAllocation(SurfaceId surfaceId, const ShadowView &shadowView) override
|
||||
{
|
||||
bool isLayoutableShadowNode = shadowView.layoutMetrics != EmptyLayoutMetrics;
|
||||
if (!isLayoutableShadowNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_;
|
||||
[scheduler.delegate schedulerOptimisticallyCreateComponentViewWithComponentHandle:shadowView.componentHandle];
|
||||
// Does nothing.
|
||||
// Preemptive allocation of native views on iOS does not require this call.
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Reference in New Issue
Block a user