Our state of the art Gantt chart


Post by yuriv »

Hello,

After implementing React cell renderers I noticed serious performance drop. See test case attached. Reproduction steps:
1) yarn && yarn start
2) Don't open Chrome devtools! With open Chrome devtools browser window may deadly freeze, so better try it first without active devtools.
3) Expand few tasks to get more rows visible and scroll back and forth, you will experience heavy UI freezes.

Despite the problems with devtools I managed to use React profiler and made a screenshot. After one single task expanded (with just few sub tasks) BryntumGantt component rendered 192 times (see screenshot attached). This seems to be the root of the issue.

In production build things become a little better, but freezes are still very perceptible. And devtools still makes dead freeze.

Please, help.

Attachments
insane-rerenders.png
insane-rerenders.png (53.97 KiB) Viewed 1722 times
gantt-react-low-performance.zip
(148.82 KiB) Downloaded 122 times

Post by mats »

Maybe start by doing a regular Performance profile so you can see what's causing this? I doubt it's something in our code.


Post by yuriv »

Attached regular performance profile of the same action - expanding one collapsed task. Made using the test case attached in the previous message.

Attachments
Profile-20201112T171329.zip
(1.41 MiB) Downloaded 132 times

Post by mats »

Ok, did you review it? Can you see the root issue of your implementation rendering it 192 times?


Post by yuriv »

I reviewed it briefly and as I can see the root issue is related to Bryntum internal logic which I'm not familiar with. If I'm wrong, please enlighten me.


Post by saki »

I have analyzed the performance with using you test case and indeed that many React components do not render fast. What is going on under the hoods is that the wrapper creates React portals as needed and releases (deletes) them to cleanup when not needed anymore.

// Hook called by engine when rendering cells, creates portals for JSX supplied by renderers
processCellContent : ({cellContent, cellElement, cellElementData, record}) => {
let shouldSetContent = cellContent != null;

// Release any existing React component
this.releaseReactCell(cellElement); // <===== here we delete the portal

// Detect React component
if (cellContent && cellContent.$$typeof === Symbol.for('react.element')) {
    // Excluding special rows for now to keep renderers simpler
    if (!record.meta.specialRow) {
        // Clear any non-react content
        const firstChild = cellElement.firstChild;
        if (!cellElementData.reactPortal && firstChild) {
            firstChild.data = '';
        }

        // Create a portal, belonging to the existing React tree but render in a cell
        const portal = ReactDOM.createPortal(cellContent, cellElement); // <==== here we create portal
        cellElementData.reactPortal = portal;

// ... etc

    releaseReactCell(cellElement) {
        const
            {state} = this,
            cellElementData = cellElement._domData;

        // Cell already has a react component in it, remove
        if (cellElementData.reactPortal) {
            state.portals.delete(cellElementData.reactPortal);

            this.setState({
                portals    : state.portals,
                generation : state.generation + 1
            });

            cellElementData.reactPortal = null;
        }
    }

The create/delete portal cycle shown to be the bottleneck also in the view of fact that it runs also when scrolling cells in/out of view.

I have made preliminary tests of another approach where we cache the created portals and re-use them and the results look promising.

The ticket is here: https://github.com/bryntum/support/issues/1869


Post by saki »

FYI, the issue has been resolved. We have introduced a config option discardPortals that is set to false by default. The rendering engine now keeps once created portals and only hides them as necessary. This saves DOM manipulation after the first portals creation so the performance is very good.

In the cases when the memory consumption is of priority, this option can be set true. In this case the portals are deleted when not needed (on scrolling, for example).

Note: The solution is not yet merged and has been slated for version 4.0.4.


Post by yuriv »

We decided to give it a try and to make new POC with React. Unfortunately, with React renderers I still see very noticeable lags during cells first initialization and also when scrolling the grid even after all cells are initialized. App this way becomes barely usable.

In my grid I have 10 columns and few hundreds rows.

Don't you have an example with React, big dataset and 10+ columns with React renderers in the grid? As I see, all examples just have react wrapper, but don't massively use react renderers.

Until you don't have such an example and according to our struggle I should declare that Bryntum Gantt doesn't seem to me really compatible with React.


Post by saki »

Please find attached performance-test app that we used when tuning performance. To use it:

  1. Download and unzip Bryntum Grid (trial) 4.0.8 from https://customerzone.bryntum.com/
  2. Download the performance-test.zip
  3. Unzip it to examples/react/javascript (besides other demos)
  4. Run the following in examples/react/javascript/performance-test
    npm i
    npm start 
  5. Navigate to https://localhost:3000

Notes:

  1. The first scroll is slower than the subsequent scrolls. This is inevitable as portals are created on the first scroll.
  2. You can change the configuration of columns and number of generated rows in src/Main.js
  3. The component used as React cell renderer is in components/CellRenderer.js

Let us please know the results.


Post by yuriv »

Could you please clarify where I can find performance-test.zip? I would try it.


Post Reply