Optimize diff algorithm to produce fewer remove+insert ("move") paired instructions

Summary:
An evolution of D20633188 but more performant.

There are three optimized paths before the slow path.

The first optimized path tries to pair identical nodes from old/new tree, and generate Update mutations, until we hit nodes that are different (indicating either a remove or an insert). This already existed.

The next two optimizations, introduced by Tim in his JS pseudocode, were inspired by ReactJS's diffing algorithm. They work in cases where the rest of the nodes are (1) all removals/deletes or (2) all creates+inserts.

Finally, if those final two optimized paths can't run, it's because there is a mix of delete+remove, create+insert, and "move" operations, mixed at the beginning, middle, and/or end of the list.

This has slightly better average/best-case complexity as the previous implementation.
In particularly pathological cases where all nodes are arbitrarily reordered, or reversed, for instance (ABCDE->EDCBA) the algorithm has the same complexity as the previous algorithm (quadratic).

For now iOS is pinned to the older differ

Changelog: [Internal] Experiment to optimize diffing algorithm in Fabric

Reviewed By: shergin

Differential Revision: D20684094

fbshipit-source-id: d29fba95a0328156c023e1c87804f23770ee1d91
This commit is contained in:
Joshua Gross
2020-03-27 19:05:36 -07:00
committed by Facebook GitHub Bot
parent 24b79c1182
commit 50a34bcd7f
7 changed files with 317 additions and 18 deletions
@@ -261,6 +261,9 @@ void Binding::installFabricUIManager(
disablePreallocateViews_ = reactNativeConfig_->getBool(
"react_fabric:disabled_view_preallocation_android");
enableOptimizedMovesDiffer_ = reactNativeConfig_->getBool(
"react_fabric:enabled_optimized_moves_differ_android");
auto toolbox = SchedulerToolbox{};
toolbox.contextContainer = contextContainer;
toolbox.componentRegistryFactory = componentsRegistry->buildRegistryFunction;
@@ -570,7 +573,9 @@ void Binding::schedulerDidFinishTransaction(
return;
}
auto mountingTransaction = mountingCoordinator->pullTransaction();
auto mountingTransaction = mountingCoordinator->pullTransaction(
enableOptimizedMovesDiffer_ ? DifferentiatorMode::OptimizedMoves
: DifferentiatorMode::Classic);
if (!mountingTransaction.has_value()) {
return;
@@ -108,6 +108,7 @@ class Binding : public jni::HybridClass<Binding>, public SchedulerDelegate {
bool collapseDeleteCreateMountingInstructions_{false};
bool disablePreallocateViews_{false};
bool disableVirtualNodePreallocation_{false};
bool enableOptimizedMovesDiffer_{false};
};
} // namespace react