Dash slow pattern-matching performance on many elements

Hello,

I recently came across a performance limitation of Dash that really made my app slow, and at some point, unusable.
The idea is that I needed to have N checkboxes that my user can check or uncheck. N can sometimes be 20, but it could actually go up to 500 or 1000. That’s where I hit some performance issue with Dash.

Here is a visual example for the problem (for 500) ; you can see a lag between my clicks and checkboxes actually being checked:
dash slow

Here is the associated code:

import uuid
import random 
import dash
from dash import dcc, html
from dash.dependencies import Input, Output, ALL

app = dash.Dash(__name__)

# We create 100 checkboxes
checkboxes = [
    dcc.Checklist(
        options=[{"label": f"Checkbox {i+1}", "value": "checked"}],
        id={"type": "checkbox", "group": random.choice(["a", "b", "c"]), "index": str(uuid.uuid4())},
        inline=True
    ) for i in range(500)
]

app.layout = html.Div([
    html.Div(id="output", style={"position": "fixed", "top": 0, "left": "200px"}),
    html.Div(checkboxes)
])

# Client-side callback to illustrate that network is not the bottleneck here
app.clientside_callback(
    """
    function(checkbox_values, checkbox_ids) {
        const groupCounts = { a: 0, b: 0, c: 0 };

        checkbox_values.forEach((value, i) => {
            if (value && value.includes('checked')) {
                const group = checkbox_ids[i].group;
                groupCounts[group]++;
            }
        });

        const totalChecked = groupCounts.a + groupCounts.b + groupCounts.c;
        return `${totalChecked} checkboxes checked (Group A: ${groupCounts.a}, Group B: ${groupCounts.b}, Group C: ${groupCounts.c})`;
    }
    """,
    Output("output", "children"),
    Input({"type": "checkbox", "group": ALL, "index": ALL}, "value"),
    Input({"type": "checkbox", "group": ALL, "index": ALL}, "id"),
)


# Run the app
if __name__ == "__main__":
    app.run_server(debug=True, port=8060)

The issue is not with the callback itself, but occurs prior to the callback. It just takes some time to gather all the checkboxes, and then fires the callback (which runs fast).

Here are some things I noticed:

  • performance drops after 100s of elements.
  • it’s still slow even if I get rid of the groups and replace string indexes with integers
  • CPU is between 90-100% and at some point the browser may ask to stop the script
  • the problem is not related to the browser. I tested on Chrome and Firefox, recent versions.
  • a similar vanilla JavaScript page (with pattern-matching-like IDs being parsed to JSON) can handle more than 100,000 checkboxes without any performance drop…

Here is the performance report (Firefox). I clicked 4 times, the CPU was around 90% all the time, making the page unresponsive:


One of the bottlenecks seems to be “getReadyCallbacks” from dash_renderer.

Do you think this could be improved in Dash?
Thank you

Hey @spriteware

hats off! (I guess this is the same @spriteware)

2 Likes

Thanks @AIMPED !
Hopefully a PR will fix this, also with the help of the plotly team.

2 Likes