Fabric: Moving layout calculation off JavaScript thread

Summary:
In the current Fabric model, we compute layout during the commit phase on the caller thread synchronously. Even though, in general, it's by design the correct way to do it, there are cases where it's *not* a requirement. In such cases, it's more optimal to yield early and continue execution of the commit process on a different thread asynchronously.
One of such cases potentially is `completeRoot` call. There we don't need to return anything and can resume JavaScript execution immediately.
The performance implications of that are not clear but there is a hope that it can free up to ~100ms of JavaScript execution time which is currently spent waiting for layout calculation (and other aspects of the commit phase).

This is an implementation in the core. The plan is to test that on iOS first and then, in case if the results of the experiment are positive, to implement it on Android as well.

Changelog: [Internal] Fabric-specific internal change.

Reviewed By: JoshuaGross

Differential Revision: D22743723

fbshipit-source-id: 846a13152c5a419de0eaef0e6283ea4277c907dc
This commit is contained in:
Valentin Shergin
2020-07-24 23:50:50 -07:00
committed by Facebook GitHub Bot
parent 650c0f64f1
commit d53fc8a3cd
7 changed files with 70 additions and 15 deletions
@@ -39,7 +39,6 @@ void MountingCoordinator::push(ShadowTreeRevision &&revision) const {
{
std::lock_guard<std::mutex> lock(mutex_);
assert(revision.getNumber() > baseRevision_.getNumber());
assert(
!lastRevision_.has_value() ||
revision.getNumber() != lastRevision_->getNumber());
@@ -79,6 +79,7 @@ Scheduler::Scheduler(
rootComponentDescriptor_ = std::make_unique<const RootComponentDescriptor>(
ComponentDescriptorParameters{eventDispatcher, nullptr, nullptr});
uiManager->setBackgroundExecutor(schedulerToolbox.backgroundExecutor);
uiManager->setDelegate(this);
uiManager->setComponentDescriptorRegistry(componentDescriptorRegistry_);
@@ -10,6 +10,7 @@
#include <ReactCommon/RuntimeExecutor.h>
#include <react/componentregistry/ComponentDescriptorFactory.h>
#include <react/core/EventBeat.h>
#include <react/uimanager/primitives.h>
#include <react/utils/ContextContainer.h>
#include <react/utils/RunLoopObserver.h>
@@ -49,6 +50,16 @@ struct SchedulerToolbox final {
*/
EventBeat::Factory asynchronousEventBeatFactory;
EventBeat::Factory synchronousEventBeatFactory;
/*
* General-purpose executor that is used to dispatch work on some utility
* queue (mostly) asynchronously to avoid unnecessary blocking the caller
* queue.
* The concrete implementation can use a serial or concurrent queue.
* Due to architectural constraints, the concrete implementation *must* call
* the call back synchronously if the executor is invoked on the main thread.
*/
BackgroundExecutor backgroundExecutor;
};
} // namespace react
@@ -290,6 +290,11 @@ UIManagerDelegate *UIManager::getDelegate() {
return delegate_;
}
void UIManager::setBackgroundExecutor(
BackgroundExecutor const &backgroundExecutor) {
backgroundExecutor_ = backgroundExecutor;
}
void UIManager::visitBinding(
std::function<void(UIManagerBinding const &uiManagerBinding)> callback)
const {
+4
View File
@@ -20,6 +20,7 @@
#include <react/mounting/ShadowTreeRegistry.h>
#include <react/uimanager/UIManagerAnimationDelegate.h>
#include <react/uimanager/UIManagerDelegate.h>
#include <react/uimanager/primitives.h>
namespace facebook {
namespace react {
@@ -41,6 +42,8 @@ class UIManager final : public ShadowTreeDelegate {
void setDelegate(UIManagerDelegate *delegate);
UIManagerDelegate *getDelegate();
void setBackgroundExecutor(BackgroundExecutor const &backgroundExecutor);
/**
* Sets and gets the UIManager's Animation APIs delegate.
* The delegate is stored as a raw pointer, so the owner must null
@@ -143,6 +146,7 @@ class UIManager final : public ShadowTreeDelegate {
UIManagerAnimationDelegate *animationDelegate_{nullptr};
UIManagerBinding *uiManagerBinding_;
ShadowTreeRegistry shadowTreeRegistry_{};
BackgroundExecutor backgroundExecutor_{};
};
} // namespace react
@@ -407,20 +407,52 @@ jsi::Value UIManagerBinding::get(
}
if (methodName == "completeRoot") {
return jsi::Function::createFromHostFunction(
runtime,
name,
2,
[uiManager](
jsi::Runtime &runtime,
const jsi::Value &thisValue,
const jsi::Value *arguments,
size_t count) -> jsi::Value {
uiManager->completeSurface(
surfaceIdFromValue(runtime, arguments[0]),
shadowNodeListFromValue(runtime, arguments[1]));
return jsi::Value::undefined();
});
if (uiManager->backgroundExecutor_) {
// Enhanced version of the method that uses `backgroundExecutor` and
// captures a shared pointer to `UIManager`.
return jsi::Function::createFromHostFunction(
runtime,
name,
2,
[uiManager, sharedUIManager = uiManager_](
jsi::Runtime &runtime,
jsi::Value const &thisValue,
jsi::Value const *arguments,
size_t count) -> jsi::Value {
auto surfaceId = surfaceIdFromValue(runtime, arguments[0]);
auto shadowNodeList =
shadowNodeListFromValue(runtime, arguments[1]);
if (sharedUIManager->backgroundExecutor_) {
sharedUIManager->backgroundExecutor_(
[sharedUIManager, surfaceId, shadowNodeList] {
sharedUIManager->completeSurface(surfaceId, shadowNodeList);
});
} else {
uiManager->completeSurface(surfaceId, shadowNodeList);
}
return jsi::Value::undefined();
});
} else {
// Basic version of the method that does *not* use `backgroundExecutor`
// and does *not* capture a shared pointer to `UIManager`.
return jsi::Function::createFromHostFunction(
runtime,
name,
2,
[uiManager](
jsi::Runtime &runtime,
jsi::Value const &thisValue,
jsi::Value const *arguments,
size_t count) -> jsi::Value {
uiManager->completeSurface(
surfaceIdFromValue(runtime, arguments[0]),
shadowNodeListFromValue(runtime, arguments[1]));
return jsi::Value::undefined();
});
}
}
if (methodName == "registerEventHandler") {
@@ -16,6 +16,9 @@
namespace facebook {
namespace react {
using BackgroundExecutor =
std::function<void(std::function<void()> &&callback)>;
struct EventHandlerWrapper : public EventHandler {
EventHandlerWrapper(jsi::Function eventHandler)
: callback(std::move(eventHandler)) {}