React: Possible regression in dev mode in v16.3

Created on 31 Mar 2018  路  18Comments  路  Source: facebook/react

This example presents a huge performance drop between React 16.2 and React 16.3.

In investigated it and noticed several things:

  • Perfomance drop is only visible using development bundle
  • This is not related to styled-components (a simple div presents the same problem)
  • I tried to investigate in performance tab of Chrome dev tools and I noticed that I get React measures even without "?react_perf"
  • React Tree Reconciliation seems very long, do not know why

It is not a big problem for me, but it looks weird, so I decided to share it on Twitter, then @gaearon invited me to submit an issue about it.

Needs Investigation Regression

Most helpful comment

Fixed in 16.3.1.

All 18 comments

Can you turn this into a smaller self-contained example without third party libs?

I will try to do it but right now I don鈥檛 have time, I think doing a setState every 16ms on 20 components in parallel should be sufficient to make it slow.

Note that if you鈥檙e using the StrictMode component, this is expected since it double renders. If you鈥檙e not, then it is not expected and probably a bug.

Here is a fairly simple comparison. Exactly the same code with different react versions:

Profile of the example provided by @alshakero

  • 16.2: 16 2
  • 16.3: 16 3

Looks like scheduling is somehow off and leaves idle gaps?

We're spending quite a bit more time in performUnitOfWork (which is the hottest path).
I think this line might be the culprit.

Want to try commenting that line out in the bundle and see if it helps?

@gaearon yep, this is it.

Good. I wonder why. Is it the allocation, the enumeration over a fiber, or changing hidden class of the target itself.

Can you try to set stashedWorkInProgressProperties to {} instead of null here and then do

Object.assign(stashedWorkInProgressProperties, workInProgress);

instead and see if it鈥檚 still as bad?

stashedWorkInProgressProperties is undefined there, so this errors out.

EDIT: Oh, I see what you want to check. Gimme a sec.

@gaearon

if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
    allocStashed();
    copyWIP();
}

function allocStashed() {
    stashedWorkInProgressProperties = {};
}

function copyWIP() {
    Object.assign(stashedWorkInProgressProperties, workInProgress);
}

zrzut ekranu 2018-04-01 o 02 02 49

Seems like it's still quite a lot. I bet we need to preserve the hidden class and explicitly write out all assignments.

Hm, maybe it's possible to write a generic function to do that based on type annotations? blazingFastClone

@gaearon an ad hoc confirmation that hidden class is the issue:

if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
    stashedWorkInProgressProperties = createFiber(
        workInProgress.tag,
        workInProgress.pendingProps,
        workInProgress.key,
        workInProgress.mode
    );
}

zrzut ekranu 2018-04-01 o 02 44 21

I forgot to actually copy.

if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
    stashedWorkInProgressProperties = createFiber(
        workInProgress.tag,
        workInProgress.pendingProps,
        workInProgress.key,
        workInProgress.mode
    );
    Object.assign(stashedWorkInProgressProperties, workInProgress);
}

This is still slow, but faster than with stashedWorkInProgressProperties = {}.

I tried locally and handwritten version seems pretty fast.

Tested on the original example and it is much better, thanks!

Fixed in 16.3.1.

Was this page helpful?
0 / 5 - 0 ratings