plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.44k stars 2.07k forks source link

Performance issue after v0.40.0 #707

Open popes01a opened 5 years ago

popes01a commented 5 years ago

We recently switched to a newer Dash version and noticed our application got considerably slower. While in Dash v0.40.0 it usually took ~20s for a page to load, in the newer versions this goes up to ~55s, or even 90s depending on the browser.

After some digging, it turns out that having a plain HTML table built using dash-html-components considerably slows the page down. We cannot use a dash-table in this case as we need links inside the table.

There is a considerable difference between v0.40.0 and all versions released after, so I'm assuming that whatever is slowing this down was added in v0.40.0. Page load timings for different dash versions and the code examples can be found below.

Any ideas why this became an issue and what can be done to fix this? Thanks!

dash v0.39.0; dash-core-components v0.44.0; dash-html-components v0.14.0; dash-renderer v0.20.0:
    - Example 1: ~1s
    - Example 2: ~2s

dash v0.40.0; dash-core-components v0.45.0; dash-html-components v0.15.0; dash-renderer v0.21.0:
    - Example 1: ~1s
    - Example 2: ~2s

dash v0.41.0; dash-core-components v0.46.0; dash-html-components v0.15.0; dash-renderer v0.22.0:
    - Example 1: ~1s
    - Example 2: ~9-10s

dash v0.42.0; dash-core-components v0.47.0; dash-html-components v0.16.0; dash-renderer v0.23.0:
    - Example 1: ~1s
    - Example 2: ~9-10s

Example 1:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash()

options = list({'label': 'City %d' % i, 'value': 'city-%d' % i} for i in range(10))

children = []
for i in range(5):
    children.extend([
        html.Label('Multi-Select Dropdown'),
        dcc.Loading(dcc.Dropdown(id='dropdown-%d' % i, options=options, value=['city-0', 'city-1'], multi=True)),
    ])

app.layout = html.Div(children)

for i in range(4):
    @app.callback(
        [Output('dropdown-%d' % (i + 1), 'options'), Output('dropdown-%d' % (i + 1), 'value')],
        [Input('dropdown-%d' % i, 'value')])
    def update_options(value):
        return [options, value]

if __name__ == '__main__':
    app.run_server(debug=True)

Example 2:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash()

options = list({'label': 'City %d' % i, 'value': 'city-%d' % i} for i in range(10))

children = []
for i in range(5):
    children.extend([
        html.Label('Multi-Select Dropdown'),
        dcc.Loading(dcc.Dropdown(id='dropdown-%d' % i, options=options, value=['city-0', 'city-1'], multi=True)),
    ])

children += [html.Tr([html.Td('Label'), html.Td(html.A('?action=enable'))])] * 800

app.layout = html.Div(children)

for i in range(4):
    @app.callback(
        [Output('dropdown-%d' % (i + 1), 'options'), Output('dropdown-%d' % (i + 1), 'value')],
        [Input('dropdown-%d' % i, 'value')])
    def update_options(value):
        return [options, value]

if __name__ == '__main__':
    app.run_server(debug=True)
0x26res commented 5 years ago

I'm having the same issue. Updating big html tables has got considerably slower since after 0.40. I had to roll back to 0.40 for now.

byronz commented 5 years ago

@popes01a thanks for the elaborated examples for performance issues. there is a major change around 0.40.0 in dash-renderer about the react component hierachy, extra props-check might adds few latency, we will investigate this furthermore.

Few suggestions you can do before that:

  1. use dash-table
  2. try the client side callback
  3. optimization can also be done on the python side children += [html.Tr([html.Td('Label '), html.Td(html.A('?action=enable'))]) for _ in range(800)] will save about 2 seconds from backend.
freshwuzhere commented 4 years ago

Same issue when I upgraded. My render time went from 0.17 seconds to 3 .1 seconds. Simple tabular data page with 20+ callbacks. Rolled back to 0.40.0 and running fine but major functionality roll back also.

alexcjohnson commented 4 years ago

Belated thanks @popes01a for the very clear investigation and report. We will try to dig into this for the next release - which is also expected to reduce the need for these big explicit html.Table components by allowing links and images in dash-table but there will of course always be cases that warrant large explicit layouts of one form or another.

popes01a commented 4 years ago

I have switched to using a Dropdown and a Continue button to trigger the action for now, but links inside a dash-table component would be ideal. Looking forward to the release!

Marc-Andre-Rivet commented 4 years ago

Testing out multiple versions of Dash with code modified from example 2 (multi-output was added in 0.39) and to avoid React warnings.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash()

options = list({'label': 'City %d' % i, 'value': 'city-%d' % i} for i in range(10))

children = []
for i in range(5):
    children.extend([
        html.Label('Multi-Select Dropdown'),
        dcc.Dropdown(id='dropdown-%d' % i, options=options, value=['city-0', 'city-1'], multi=True),
    ])

rows = [html.Tr([
    html.Td('Label'),
    html.Td(html.A('?action=enable'))
])] * 800

children += [html.Table([html.Tbody(rows)])]

app.layout = html.Div(children)

for i in range(4):
    @app.callback(
        Output('dropdown-%d' % (i + 1), 'options'),
        [Input('dropdown-%d' % i, 'value')])
    def update_options(value):
        return options

    @app.callback(
        Output('dropdown-%d' % (i + 1), 'value'),
        [Input('dropdown-%d' % i, 'value')])
    def update_options(value):
        return value

if __name__ == '__main__':
    app.run_server(debug=False)

I get the following times for debug=False and dropdown count=5, average of 3 runs.

Dash version Network ready CPU time
0.35 6.67s 9.44s
0.40 2.14s 3.27s
0.42 6.57s 8.60s
1.7.0 7.24s 9.36s

Looks like 0.35, 0.42, 1.7.0 all have similar performance and 0.40 stands on its own. Will look into running the perf test against more versions and looking more deeply into what's happening during render that would explain the performance differences.

0x26res commented 4 years ago

I've played around with the latest version of dash, and I don't notice the performance issue I was experiencing with 0.41.0+ any more. Maybe this issue has been solved somehow?

dash==1.13.3
dash-html-components==1.0.3
alexcjohnson commented 4 years ago

@arthurandres great! We did improve performance a bunch in #1254 - @Marc-Andre-Rivet when you're back you can extend the table above, and perhaps close this issue?