Text slow to update next to a Figure

I am finding that it is CPU intensive to update text, inside PreText for example, next to a figure.

I think it could be related to this Bug report:

I have tested this on two computers:

  • Raspberry Pi 3B+

    • OS: Raspbian stretch 9.11
    • Python: 3.7.3
    • Bokeh: 2.0.2
    • Browser: Chromium 72.0.3626.121
  • Desktop PC:

    • OS: Ubuntu 18.04.4
    • Python: 3.6.7
    • Bokeh: 2.0.2
    • Browser: Chromium 83.0.4103.61 and Firefox 76.0.1

Example code is:

# standard lib
import random
import time

# Bokeh imports
from bokeh.models import ColumnDataSource, PreText, Div, Paragraph, Spacer
from bokeh.layouts import row, column, gridplot
from bokeh.plotting import curdoc, figure

show_figures = True
update_figures = True
show_text = True
update_text = True

# size of starting data
start_npts = 2000

# size of new data to send at update
update_npts = 5

# Update time in ms
figure_update_time = 5000
text_update_time = 500

# Backend to use for figures
figure_backend = 'webgl'   # 'canvas' or 'webgl'

count = 0

doc = curdoc()

# setup figures with some starting data from the past
plots = [{} for i in range(4)]

t = time.time()

for p in plots:
    # time is in ms
    p['data'] = {'value': [random.random()*100 for i in range(start_npts)], 'time': [(t-i*1.0)*1000 for i in range(start_npts)]}
    p['source'] = ColumnDataSource(data=p['data'])
    p['figure'] = figure(plot_width=300, plot_height=100,
                         sizing_mode='stretch_both',
                         x_axis_type='datetime',
                         output_backend=figure_backend)
    p['figure'].square(x='time', y='value', source=p['source'], size=5, color="black")


# arrange figures in a column via gridplot
fig_col = gridplot([p['figure'] for p in plots], ncols=1, toolbar_location='left')


# Setup and arrange text in column
static_text = PreText(text="This text does not change\n", width=200, height=100, sizing_mode='stretch_both')
dynamic_text = PreText(text="This text changes\n", width=200, height=100, sizing_mode='stretch_both')

text_col = column([static_text, dynamic_text])

# Spacer
space = Spacer(width=200, height=200)

# group fig and text in a row
doc_layout = row([fig_col if show_figures else space, text_col if show_text else space], sizing_mode='stretch_height')

# doc_layout = text_col

# add group layout to the document
doc.add_root(doc_layout)


def figure_update_func():
    for p in plots:
        new_data = {
            'value': [random.random()*100 for n in range(update_npts)],
            'time': [time.time()*1000+n*100 for n in range(update_npts)],
        }
        p['source'].stream(new_data)

    print(len(plots[0]['data']['time']))


def text_update_func():
    global count

    count += 1
    print(count)

    dynamic_text_buff = "This text changes\n"
    dynamic_text_buff += "Counter: {}\n".format(count)
    dynamic_text_buff += "Number of points in each figure: {}\n".format(len(plots[0]['data']['time']))
    dynamic_text.text = dynamic_text_buff


if update_figures and show_figures:
    doc.add_periodic_callback(figure_update_func, figure_update_time)

if update_text and show_text:
    doc.add_periodic_callback(text_update_func, text_update_time)


If I display and update just the text (show_figures = False, update_figures = False, show_text = True, update_text = True) the web browser uses very little CPU.
The CPU usage for the Raspberry Pi can be seen in the top right corner (Blue) on the screenshot below.

If i add a static figure next to it (show_figures = True, update_figures = False, show_text = True, update_text = True) then the CPU use increases significantly.

I observed this behaviour on my desktop PC as well but I needed to add more figures in the figure column, 10+ (at 40 it was using up an entire core).
I observed this both in Firefox and Chromium, although the Chromium did use a bit less CPU then Firefox.
In contrast to the above mentioned Bug.

Adding more points to the figures did not have any effect.
Neither did changing the PreText to Div or Paragraph, or using HTML5 canvas instead of WebGL.

For completeness I include a screenshots of just updating the figures (show_figures = True, update_figures = True, show_text = True, update_text = False),
and of updating the figures and text (show_figures = True, update_figures = True, show_text = True, update_text = False)

I also tried explicitly setting all the elements (p['figure'], fig_col, static_text, dynamic_text, text_col, doc_layout) to sizing_mode='fixed' and it did not help.

I looked a bit into the Chromium and Firefox inspector performance and there seems to be a lot of measure, _measure, _measure_cells, _measure_grid calls.
I do not know enough to interpret this correctly.

Any idea what could be causing this and how I could avoid this high CPU use when updating text?

Seems like this post would be of interest to you: Layout guidelines/tips for rapidly rendering many plots

1 Like

I can’t believe I missed that link when reading about bug #9519

So, I read both threads carefully, and while modifying the Div widget could work it is not something I would like to play around with at this point.
I have little to no experience with JavaScript.

But I did notice something in one comment of the bug #9519 thread.

Based on advice from @bryevdv I decomposed the main layout to into 7 columns and added each of these to the document as independent calls to doc.add_root . By doing so it reduced calls to compute_layout from 4 to 2, and those calls now take 1.5 and 1.2 seconds respectively.

So I tried using:

doc.add_root(fig_col)
doc.add_root(text_col)

and it worked. No more unexpectedly high CPU use.
I did not realize you could call add_root() more than once. There is usually only one root :smiley:.
I am guessing this works because compute_layout() only goes up to the root, not the whole document?

To fix the layout I made a quick Jinja template:

{% extends base %}

{% block contents %}
<div style=" width: 95vw; height: 95vh; display: flex">
    <div style="flex: 1 0 auto">
        {{ embed(roots.fig_col) }}
    </div>
    <div style="flex: 0 0 auto">
        {{ embed(roots.text_col) }}
    </div>
</div>
{% endblock %}

It sort of works. The initial size of the elements is wrong and I need to manually resize the browser for flex to resize everything properly, but it’s a start.
I’ll let you know if i figure out anything better.

@Marculius

You should be able to try the alternative Div mentioned at the bottom of the thread to see if it helps in your scenario without having to do any JavaScript or Typescript programming. It is meant to be a replacement for the Bokeh Div with identical syntax; it just needs to be imported and used instead of Div.

@_jm

I tried using it, but custom widgets require node.js to compile.
I don’t feel like setting it up, given that I have another working solution (I’m very lazy :grin: ).

My setup works great for me, since my elements shouldn’t change size once setup.

To solve the sizing issues I mentioned previously,
I just need to add ‘text_col’ first since based on it’s width should the width of ‘fig_col’ be setup.

doc.add_root(text_col)
doc.add_root(fig_col)

If I add fig_col first it would just expand to the size of entire flex container.

All I have to make sure going forward is that the text width stays smaller or equal to the max width of the initial text.

1 Like

Note that #10113 was just merged so there should be improvement in text widget performance in the next (2.1) release.

2 Likes