You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The profiles are here. Zipped because the jsons were refusing to upload properly for some reason. Profiles are with dev tools enables, but the same situation happens when deployed with dev tools disabled. (replaced with MWE)
This MWE (not really minimal, but I hope this is simple enough) replicates the issue. On my M1 macbook pro, with dash 2.18 the page works without issue, tooltips are responsive as expected. On dash 3.0, the page is extremely laggy, tooltips are taking a second or two to appear and then often failing to disappear. I tried setting dash._dash_renderer._set_react_version("18.2.0") on dash 2.18, and it still worked ok, so I expect the problem is somewhere in the new version of dash.
from random import random
import dash_bootstrap_components as dbc
from dash import Dash, Input, Output, callback, dcc, html
APP_NAME = "mwe"
row_num = 8
def get_div_with_tooltip(id_prefix: str, variable: str, rc: str, inout: str, tooltip_title: str) -> dbc.Col:
div_id = {"app_name": id_prefix, "component": "div", "var": variable, "rc": rc, "inout": inout}
tool_tip_id = {**div_id, "component": "div_tooltip"}
title = f"{tooltip_title}\n{rc} ({rc}): no data"
div = html.Div("no data", id=div_id)
tooltip = dbc.Tooltip(children=title, id=tool_tip_id, target=div_id, trigger="hover", placement="bottom", style={"white-space": "pre"})
div_col = dbc.Col([div, tooltip], width="auto")
return div_col
def get_spread_div_with_tooltip(id_prefix: str, variable: str, rc: str, title_prefix: str, use_bold: bool) -> dbc.Col:
div_id = {"app_name": id_prefix, "component": "div", "var": variable, "rc": rc, "inout": "rc"}
tool_tip_id = {**div_id, "component": "div_tooltip"}
title = f"{title_prefix}\n\nin\n\nout"
div = html.H5(html.B("no data", id=div_id)) if use_bold else html.Div("no data", id=div_id)
tooltip = dbc.Tooltip(children=title, id=tool_tip_id, target=div_id, trigger="hover", placement="bottom", style={"white-space": "pre"})
div_col = dbc.Col([div, tooltip], width="auto")
return div_col
def get_card_title(id_prefix: str, rc: str) -> html.Div:
title_id = {"app_name": id_prefix, "component": "div", "var": "title_isos", "rc": rc, "inout": "rc"}
title_b = html.B(rc, id=title_id)
f_title = html.B("F: \n")
f_tooltip_text = "test"
f_tooltip = dbc.Tooltip(
children=[f_title, f_tooltip_text], target=title_id, trigger="hover", placement="bottom", style={"white-space": "pre"}
)
icon_table = html.I(className="bi bi-table", style={"color": "rgba(0, 0, 0, 0.5)", "padding": "0", "display": "inline-flex"})
icon_button_id = {"app_name": id_prefix, "component": "button", "var": "table-icon", "rc": rc}
table_button = dbc.Button(children=[icon_table], id=icon_button_id, style={"backgroundColor": "transparent", "border": "0px"})
title_row = dbc.Row(
[
dbc.Col([], align="start", width="auto"),
dbc.Col([title_b, f_tooltip], align="center", width="auto"),
dbc.Col(table_button, align="end", width="auto"),
],
justify="between",
)
card_title = html.Div(
[title_row],
className="card-title my-0",
)
return card_title
def get_single_card(id_prefix: str, rc: str) -> dbc.Card | str:
x_in = get_div_with_tooltip(id_prefix, "d", rc, "in", "XXX")
x_out = get_div_with_tooltip(id_prefix, "d", rc, "out", "XXX")
x_avg_in = get_div_with_tooltip(id_prefix, "r", rc, "in", "XXX")
x_avg_out = get_div_with_tooltip(id_prefix, "r", rc, "out", "XXX")
x_s = get_spread_div_with_tooltip(id_prefix, "5", rc, "XXX", True)
y_s = get_spread_div_with_tooltip(id_prefix, "o", rc, "XXX", False)
card_title = get_card_title(id_prefix, rc)
card_body = html.Div(
[
dbc.Row([x_in, x_out], justify="between"),
dbc.Row([x_s], justify="around"),
dbc.Row([x_avg_in, y_s, x_avg_out], justify="between"),
],
className="card-text",
)
card_line = html.Hr(style={"margin-top": "5px"})
card_id = {"app_name": id_prefix, "component": "card", "var": "color", "rc": rc, "inout": "rc"}
card = dbc.Card([card_title, card_line, card_body], body=True, color="#FFFFFF", className="my-2", id=card_id)
return card
layout = dbc.Container(
[
*(dbc.Row([dbc.Col(get_single_card(APP_NAME, f"{r}_{c}")) for c in range(5)]) for r in range(row_num)),
dcc.Interval(
id={"app_name": APP_NAME, "id": "interval"},
interval=1000,
n_intervals=0,
),
],
# fluid=True,
)
@callback(
output={
f"{r}_{c}": [
Output({"app_name": APP_NAME, "component": "div", "var": "5", "rc": f"{r}_{c}", "inout": "rc"}, "children"),
Output(
{"app_name": APP_NAME, "component": "div_tooltip", "var": "5", "rc": f"{r}_{c}", "inout": "rc"},
"children",
),
Output({"app_name": APP_NAME, "component": "card", "var": "color", "rc": f"{r}_{c}", "inout": "rc"}, "color"),
Output({"app_name": APP_NAME, "component": "div", "var": "5", "rc": f"{r}_{c}", "inout": "rc"}, "style"),
]
for r in range(row_num)
for c in range(5)
},
inputs=[
Input({"app_name": APP_NAME, "id": "interval"}, "n_intervals"),
],
)
def update1(_) -> dict[str, tuple[str, str, str, dict]]:
data = {f"{r}_{c}": (f"{random() :.2f}", f"{random() :.2f}", "rgba(0, 0, 0, 0.5)", {}) for r in range(row_num) for c in range(5)}
return data
@callback(
output={
f"{r}_{c}": [
Output({"app_name": APP_NAME, "component": "div", "var": "o", "rc": f"{r}_{c}", "inout": "rc"}, "children"),
Output(
{"app_name": APP_NAME, "component": "div_tooltip", "var": "o", "rc": f"{r}_{c}", "inout": "rc"},
"children",
),
Output({"app_name": APP_NAME, "component": "div", "var": "o", "rc": f"{r}_{c}", "inout": "rc"}, "style"),
]
for r in range(row_num)
for c in range(5)
},
inputs=[
Input({"app_name": APP_NAME, "id": "interval"}, "n_intervals"),
],
)
def update2(_) -> dict[str, tuple[str, str, dict]]:
data = {f"{r}_{c}": (f"{random() :.2f}", f"{random() :.2f}", {}) for r in range(row_num) for c in range(5)}
return data
@callback(
output={
(f"{r}_{c}", inout): [
Output({"app_name": APP_NAME, "component": "div", "var": "r", "rc": f"{r}_{c}", "inout": inout}, "children"),
Output(
{"app_name": APP_NAME, "component": "div_tooltip", "var": "r", "rc": f"{r}_{c}", "inout": inout},
"children",
),
]
for r in range(row_num)
for c in range(5)
for inout in ["in", "out"]
},
inputs=[
Input({"app_name": APP_NAME, "id": "interval"}, "n_intervals"),
],
)
def update3(_) -> dict[tuple[str, str], tuple[str, str]]:
data = {(f"{r}_{c}", inout): (f"{random() :.2f}", f"{random() :.2f}") for r in range(row_num) for c in range(5) for inout in ["in", "out"]}
return data
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = layout
app.run()
Hey @tlauli, I do see a difference in performance for Dash=3 and Dash<3 when loading tooltips. I wonder if this is related to #3066 which changes how components are rendered & updated. @T4rk1n would be able to give a better read here.
@BSd3v I tried your branch, the performance is better than in 3.0, but still noticeably worse than in 2.18. The tooltips still feel laggy and are failing to hide pretty often.
This is due to the difference in how Dash 3 renders vs 2.
2 renders the whole app for each component change, the more components you are adjusting at the same time, the more efficient this becomes.
3 renders the individual components and children, if their props were altered. This is more performant when not modifying a massive amount of components at a single time.
As a note, the tooltip is querying the context of the loading of the target component, this is done in a different way. And might be better to determine if the component is mounted vs loading. This change would have to be done in DBC.
This is due to the difference in how Dash 3 renders vs 2.
2 renders the whole app for each component change, the more components you are adjusting at the same time, the more efficient this becomes.
3 renders the individual components and children, if their props were altered. This is more performant when not modifying a massive amount of components at a single time.
Ok, this is a bit unfortunate for me. But it makes sense to not render the whole app when a single prop changes.
Would it be possible to somehow limit the number of renders? Rendering the parent instead of most of its children? Preventing the render if the prop is adjusted to the same value it was?
As for the current situation, I think I can work around the perf issue by sending no_updates for the values that stayed the same (which is most of the time all of them), if I understand this correctly. But it would be nice not having to hack it in user code.
If you refactor the code to update the whole card at a single time, it may help.
Or even using a patch update on the containing div. I know its more tricky to navigate from the app build.
My PR originally tested for component props being the same and not doing anything, however, there are sometimes that the props dont change and the app needed to rerender the component. There are components that count how many times they render, even though their props stay the same, they needed to rerender.
Anyways, I think refactoring might work, that way its rendering from the top down, instead of each individual component. 😄
Yeah, we did it this way because there are 5 types of data in a single card, and the data is partitioned by these types, instead of the card it belongs to. Also I think the Tooltip disappears when the components it belongs to is replaced in the DOM under the mouse pointer, which is really annoying to users, but I may be confusing this with something else.
With the improvements in the linked PR I think we can refactor our code so the page runs acceptably, so thanks for the optimizations and pointing out what we should improve in our code. ❤
Activity
tlauli commentedon Mar 25, 2025
The profiles are here. Zipped because the jsons were refusing to upload properly for some reason. Profiles are with dev tools enables, but the same situation happens when deployed with dev tools disabled.(replaced with MWE)tlauli commentedon Mar 26, 2025
This MWE (not really minimal, but I hope this is simple enough) replicates the issue. On my M1 macbook pro, with dash 2.18 the page works without issue, tooltips are responsive as expected. On dash 3.0, the page is extremely laggy, tooltips are taking a second or two to appear and then often failing to disappear. I tried setting
dash._dash_renderer._set_react_version("18.2.0")on dash 2.18, and it still worked ok, so I expect the problem is somewhere in the new version of dash.ndrezn commentedon Mar 26, 2025
Hey @tlauli, I do see a difference in performance for Dash=3 and Dash<3 when loading tooltips. I wonder if this is related to #3066 which changes how components are rendered & updated. @T4rk1n would be able to give a better read here.
BSd3v commentedon Mar 31, 2025
Hey @tlauli,
I believe that my PR may address this: #3248
[-][BUG] Performance regression in dash 3.0[/-][+]Performance regression in dash 3.0[/+]tlauli commentedon Apr 1, 2025
@BSd3v I tried your branch, the performance is better than in 3.0, but still noticeably worse than in 2.18. The tooltips still feel laggy and are failing to hide pretty often.
BSd3v commentedon Apr 1, 2025
This is due to the difference in how Dash 3 renders vs 2.
2 renders the whole app for each component change, the more components you are adjusting at the same time, the more efficient this becomes.
3 renders the individual components and children, if their props were altered. This is more performant when not modifying a massive amount of components at a single time.
BSd3v commentedon Apr 1, 2025
As a note, the tooltip is querying the context of the loading of the target component, this is done in a different way. And might be better to determine if the component is mounted vs loading. This change would have to be done in DBC.
tlauli commentedon Apr 1, 2025
Ok, this is a bit unfortunate for me. But it makes sense to not render the whole app when a single prop changes.
Would it be possible to somehow limit the number of renders? Rendering the parent instead of most of its children? Preventing the render if the prop is adjusted to the same value it was?
As for the current situation, I think I can work around the perf issue by sending
no_updates for the values that stayed the same (which is most of the time all of them), if I understand this correctly. But it would be nice not having to hack it in user code.BSd3v commentedon Apr 1, 2025
If you refactor the code to update the whole card at a single time, it may help.
Or even using a patch update on the containing div. I know its more tricky to navigate from the app build.
My PR originally tested for component props being the same and not doing anything, however, there are sometimes that the props dont change and the app needed to rerender the component. There are components that count how many times they render, even though their props stay the same, they needed to rerender.
Anyways, I think refactoring might work, that way its rendering from the top down, instead of each individual component. 😄
tlauli commentedon Apr 1, 2025
Yeah, we did it this way because there are 5 types of data in a single card, and the data is partitioned by these types, instead of the card it belongs to. Also I think the Tooltip disappears when the components it belongs to is replaced in the DOM under the mouse pointer, which is really annoying to users, but I may be confusing this with something else.
With the improvements in the linked PR I think we can refactor our code so the page runs acceptably, so thanks for the optimizations and pointing out what we should improve in our code. ❤
T4rk1n commentedon Apr 15, 2025
Look like the code can now run acceptably, there might still be improvement to be made to the system but this is mostly resolved.