Fix DEV performance regression by avoiding Object.assign on Fibers (#12510)
* Fix DEV performance regression by avoiding Object.assign on Fibers * Reduce allocations in hot path by reusing the stash Since performUnitOfWork() is not reentrant, it should be safe to reuse the same stash every time instead of creating a new object.
This commit is contained in:
parent
0c80977061
commit
59dac9d7a6
|
@ -471,3 +471,47 @@ export function createFiberFromPortal(
|
||||||
};
|
};
|
||||||
return fiber;
|
return fiber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used for stashing WIP properties to replay failed work in DEV.
|
||||||
|
export function assignFiberPropertiesInDEV(
|
||||||
|
target: Fiber | null,
|
||||||
|
source: Fiber,
|
||||||
|
): Fiber {
|
||||||
|
if (target === null) {
|
||||||
|
// This Fiber's initial properties will always be overwritten.
|
||||||
|
// We only use a Fiber to ensure the same hidden class so DEV isn't slow.
|
||||||
|
target = createFiber(IndeterminateComponent, null, null, NoContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is intentionally written as a list of all properties.
|
||||||
|
// We tried to use Object.assign() instead but this is called in
|
||||||
|
// the hottest path, and Object.assign() was too slow:
|
||||||
|
// https://github.com/facebook/react/issues/12502
|
||||||
|
// This code is DEV-only so size is not a concern.
|
||||||
|
|
||||||
|
target.tag = source.tag;
|
||||||
|
target.key = source.key;
|
||||||
|
target.type = source.type;
|
||||||
|
target.stateNode = source.stateNode;
|
||||||
|
target.return = source.return;
|
||||||
|
target.child = source.child;
|
||||||
|
target.sibling = source.sibling;
|
||||||
|
target.index = source.index;
|
||||||
|
target.ref = source.ref;
|
||||||
|
target.pendingProps = source.pendingProps;
|
||||||
|
target.memoizedProps = source.memoizedProps;
|
||||||
|
target.updateQueue = source.updateQueue;
|
||||||
|
target.memoizedState = source.memoizedState;
|
||||||
|
target.mode = source.mode;
|
||||||
|
target.effectTag = source.effectTag;
|
||||||
|
target.nextEffect = source.nextEffect;
|
||||||
|
target.firstEffect = source.firstEffect;
|
||||||
|
target.lastEffect = source.lastEffect;
|
||||||
|
target.expirationTime = source.expirationTime;
|
||||||
|
target.alternate = source.alternate;
|
||||||
|
target._debugID = source._debugID;
|
||||||
|
target._debugSource = source._debugSource;
|
||||||
|
target._debugOwner = source._debugOwner;
|
||||||
|
target._debugIsCurrentlyTiming = source._debugIsCurrentlyTiming;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ import {
|
||||||
startCommitLifeCyclesTimer,
|
startCommitLifeCyclesTimer,
|
||||||
stopCommitLifeCyclesTimer,
|
stopCommitLifeCyclesTimer,
|
||||||
} from './ReactDebugFiberPerf';
|
} from './ReactDebugFiberPerf';
|
||||||
import {createWorkInProgress} from './ReactFiber';
|
import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
|
||||||
import {onCommitRoot} from './ReactFiberDevToolsHook';
|
import {onCommitRoot} from './ReactFiberDevToolsHook';
|
||||||
import {
|
import {
|
||||||
NoWork,
|
NoWork,
|
||||||
|
@ -267,7 +267,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||||
stashedWorkInProgressProperties = null;
|
stashedWorkInProgressProperties = null;
|
||||||
replayUnitOfWork = (failedUnitOfWork: Fiber, isAsync: boolean) => {
|
replayUnitOfWork = (failedUnitOfWork: Fiber, isAsync: boolean) => {
|
||||||
// Retore the original state of the work-in-progress
|
// Retore the original state of the work-in-progress
|
||||||
Object.assign(failedUnitOfWork, stashedWorkInProgressProperties);
|
assignFiberPropertiesInDEV(
|
||||||
|
failedUnitOfWork,
|
||||||
|
stashedWorkInProgressProperties,
|
||||||
|
);
|
||||||
switch (failedUnitOfWork.tag) {
|
switch (failedUnitOfWork.tag) {
|
||||||
case HostRoot:
|
case HostRoot:
|
||||||
popHostContainer(failedUnitOfWork);
|
popHostContainer(failedUnitOfWork);
|
||||||
|
@ -846,7 +849,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
|
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
|
||||||
stashedWorkInProgressProperties = Object.assign({}, workInProgress);
|
stashedWorkInProgressProperties = assignFiberPropertiesInDEV(
|
||||||
|
stashedWorkInProgressProperties,
|
||||||
|
workInProgress,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let next = beginWork(current, workInProgress, nextRenderExpirationTime);
|
let next = beginWork(current, workInProgress, nextRenderExpirationTime);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2013-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @jest-environment node
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe('ReactIncrementalErrorReplay-test', () => {
|
||||||
|
it('copies all keys when stashing potentially failing work', () => {
|
||||||
|
// Note: this test is fragile and relies on internals.
|
||||||
|
// We almost always try to avoid such tests, but here the cost of
|
||||||
|
// the list getting out of sync (and causing subtle bugs in rare cases)
|
||||||
|
// is higher than the cost of maintaing the test.
|
||||||
|
const {
|
||||||
|
// Any Fiber factory function will do.
|
||||||
|
createHostRootFiber,
|
||||||
|
// This is the method we're going to test.
|
||||||
|
// If this is no longer used, you can delete this test file.
|
||||||
|
assignFiberPropertiesInDEV,
|
||||||
|
} = require('../ReactFiber');
|
||||||
|
|
||||||
|
// Get a real fiber.
|
||||||
|
const realFiber = createHostRootFiber(false);
|
||||||
|
const stash = assignFiberPropertiesInDEV(null, realFiber);
|
||||||
|
|
||||||
|
// Verify we get all the same fields.
|
||||||
|
expect(realFiber).toEqual(stash);
|
||||||
|
|
||||||
|
// Mutate the original.
|
||||||
|
for (let key in realFiber) {
|
||||||
|
realFiber[key] = key + '_' + Math.random();
|
||||||
|
}
|
||||||
|
expect(realFiber).not.toEqual(stash);
|
||||||
|
|
||||||
|
// Verify we can still "revert" to the stashed properties.
|
||||||
|
expect(assignFiberPropertiesInDEV(realFiber, stash)).toBe(realFiber);
|
||||||
|
expect(realFiber).toEqual(stash);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue