Performance of layout engine

I am developing since a couple of days complex/multi-tabbed
layouts in Panel. I am trying to get my head around what is
actually happening in terms of re-drawing.

I observed that about 90 % of the time is spent outside
of my own event-handlers (they perform Pandas data wrangling etc.),
but simply re-drawing the layout.

I observed that the closer to the document root one changes
the layout the more drastic the performance slow-down is . Say
we have a top-level Tab layout and we dynamically add new tabs
in response to user actions. In my tests this could lead to
mis-render or even crash of the dashboard.

I understand from reading a Github issue on Bokeh that this slowness
is caused by recomputation of widget sizes. Will this be fixed in Bokeh 3.0
and when will this propagate to Panel?

1 Like

My current work-around is to keep the layout more
or less static close to the document root, and only replace widgets with
no children, such as leaf-level plots or not replace objects
but only over-writing param.values of widgets.

1 Like

This will be solved with bokeh 3.0, and it is being translated to panel in this pr

2 Likes

Ok, that’s great to hear. When is this release planned to happen? I think Bokeh 3 is released
on 24th October.

There’s nothing really complex about my app data-wise, except
the .options argument of MultiSelect widget can be quite large. Could
this be an issue in terms of rendering?

Yes Bokeh 3 should be released shortly. It is major release that comes with breaking changes and even if the Panel maintainers have anticipated that (testing out dev releases of Bokeh 3 and providing feedback) there will still be quite some work to do before being able to release a version of Panel that depends on Bokeh 3.

In the mean time it’d be great if you could provide the code of a small app that reproduces the performance impacts you’re observing.

This might help. Try adding dynamic=True when you instantiate the Tabs.

https://panel.holoviz.org/reference/layouts/Tabs.html#dynamic

I just found this to drastically speed up my app with lots of tabs!

1 Like

Also Panel 1.x/ Bokeh 3.x has dramatically changed the layout performance. Now you should be able to have large, dynamic layouts without issues. If not please issues on Github.

1 Like

One observation from my side doing some tests 2 weeks ago to understand why redrawing my layout with 10-20 rows (each containing some text, a button and a few plots) takes long to finish redrawing on the browser:

If you have lots of panel.pane.Str, panel.pane.HTML or Markdowns this slows down the redrawing if you do not fully clear the container (eg. Column), then prepare the new content and add all in one go.

Fast: .
my_main_column.extend([new_rows])
my_main_column.objects = new_rows

Slow: anything using append
for row in new_rows:
my_main_column.append(new_row)

Problem doesn’t happen with redrawing widgets, bokeh plots etc. just with panel.pane Text stuff.

The code finishes same time, but the browser update takes much longer (and redrawing the text is quite visible). Doesn’t matter if MS-EDGE or FIREFOX.

Here some test-code for JupyterLab (but starts a panel server) to separate the display.

import numpy as np

import panel as pn
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource

pn.extension()

ui = pn.Column()

def update_layout(
    container, buttons=1, plots=3, option=1,
    color='red', button_type='danger'
):
    container.clear()

    if plots > 0:
        scatter_data = {'x': np.random.random(100), 
                        'y': np.random.random(100)}

    new_layout = []
    
    for row in range(20):

        ui_row = pn.Row(
                        f' STRING {row}', 
                        f'# MARKDOWN {row}', 
                        pn.pane.HTML(f'<H1>HTML {row}</H1>'), 
                        styles={'background': color})

        for button in range(buttons):
            ui_row.append(
                pn.widgets.Button(name=color, button_type=button_type))
     
        for plot in range(plots):
            p = figure(title='title', width=200, height=100, tools='hover')
            source = ColumnDataSource(scatter_data)
            p.scatter(x='x', y='y', color=color, source=source)
            ui_row.append(p)

        if option == 1:
            # option 1: worse performance
            container.append(ui_row)
        elif option == 2:
            # option 2( part1): better performance
            new_layout.append(ui_row)

    if option == 2:
        # option 2 (part2) better performance
        container.objects = new_layout
    
    return container

# create layout and server
update_layout(container=ui, buttons=1, plots=3, option=1, color='red', button_type='danger')
ui.show()

# sampel commands to test redrawing behaviour
option = 1   ### 1 ... slow, 2 ... fast
buttons = 0   ##  number of buttons to draw
plots = 0   ## number of plots to draw

update_layout(container=ui, buttons=buttons, plots=plots, option=option,
              color='red', button_type='danger')
print('done')

# wait and try again
update_layout(container=ui, buttons=buttons, plots=plots, 
              option=option, color='lightblue', button_type='warning')
print('done1')

@johann I think .append is slow because each .append triggers a re-render.

In general it would be nice to have some guide explaining this and with tips on how to minimize the number of updates (by combining events and structuring the code adequately)

1 Like

I agree .append being slow because it triggers many updates.