bokeh / bokeh

Interactive Data Visualization in the browser, from Python

Home Page:https://bokeh.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

repaint() in plot_canvas.coffee causing significant lag

cutright opened this issue · comments

I've had significant lag in my Bokeh app since 0.12.11, and I've been opting out of updating my code. This issue persists in 0.12.15. I've done some investigation by installing individual commits.

I'm using:
Mac OS 10.13.3
Chrome Version 64.0.3282.186 (Official Build) (64-bit)

Nov 15, 2017

The most recent commit with no lag issues for me: a14babf
The next commit causes significant lag when updating figures 3edfdaf

I found that simply reverting line 475 from:
@connect(@force_paint, () => @repaint())
back to
@connect(@force_paint, () => @paint())
Resolves all of my issues

@connect(@force_paint, () => @repaint())

Feb 8, 2018

If i follow the commits all the way up until right before the Selection class is introduced, I can resolve my lag issues in the same manner.
If I install commit 5a122da
And change
this.connect(this.force_paint, () => this.repaint());
to
this.connect(this.force_paint, () => this.paint());
My lag issue goes away

this.connect(this.force_paint, () => this.repaint());

Comments

The introduction of the Selection class is causing additional lag issues for me, but I'm not yet sure if they can be resolved by updating my own code rather than Bokeh source code. I will post a separate issue if I find out more. But this is why I stopped checking at commit 5a122da

There is also something causing significant delays in the initial launch of the web view that I did not used to have, but I haven't tracked the issue down. For sure though, repaint() to paint() as indicated above helps significantly.

JavaScript console output

bug-fixed_GitHub_localhost-1523218296792.log
bug_GitHub_localhost-1523218296792.log

Screenshots or screencasts of the bug in action

https://www.dropbox.com/s/5ojvuldj7wal4k5/bug-fixed_GitHub_localhost-1523218296792.mov?dl=0
https://www.dropbox.com/s/hlso65k322v10eh/bug_GitHub_localhost-1523218296792.mov?dl=0

cc @mattpap @clairetang6 comments appreciated

Obviously replacing repaint() with paint() is no fix, because that would bring back #4394 and possibly other issues. The underlying problem is that with the number of layout elements used here, the currently implemented layout is just way too slow and unlikely to get significantly better.

I agree I'm probably pushing Bokeh to its limits, but there aren't too many layout elements for commit a14babf ? Clearly something about repaint() is much more inefficient compared to paint(), I could produce JS console logs for 3edfdaf with and without out repaint if that would help?

repaint() is a bit misleading name. What it does is it checks whether plot changed enough that full layout computation is needed and performs one, or else just goes directly to paining (so paint()). There is a chance that there is a bug in the detection code, e.g. it may be to eager to recompute.

There is a chance that there is a bug in the detection code, e.g. it may be to eager to recompute.

This seems like a good first avenue of investigation

Same issue here. I made some tests and this were my perceptions:

  • Bokeh version <= 0.12.10 - smooth (panning and zooming)
  • Bokeh version >= 0.12.11 - laggy (panning and zooming)
  • The lag is always with the same degree, plotting 10 or 30 plots
  • The number of samples is not affecting the lag
  • The laggy behaviour is more noticeable if I zoom out far away and I pan the glyph
  • Sometimes the y axis of the plots where I am panning, and the axis of the plot above or below, change their position some pixels to the left or to the right

I am working with a GeForce GTX 1060

Minimal example with lag:

from bokeh.models import CustomJS, ColumnDataSource, Circle, CDSView, IndexFilter
from bokeh.plotting import figure
from bokeh.layouts import layout, gridplot
from bokeh.io import curdoc
import numpy as np

N = 300
x = np.random.random(size=N)
y = np.random.random(size=N)

source = ColumnDataSource(data=dict(x=x, y=y))

plots = []
for i in range(10):
    plots.append(figure(width=400, height=400, tools='pan,wheel_zoom', output_backend='webgl', ))
    plots[i].circle(x='x', y='y', source=source, )

gp = gridplot(
    children=plots,
    ncols=4,
    plot_width=400,
    plot_height=400,
    toolbar_location='left',
)

curdoc().add_root(gp)

I think the lag is not noticeable in the following gif, but you can check how the axis is moved

(...) but you can check how the axis is moved.

Just to make it clear, axis movement is expected if width/height of labels (or other factors) change. In this case when minus sign appears. After a quick verification, the layout recomputation is requested the correct number of times, so the likely reason for the slowdown is the size of the layout and the cost of repainting plots. Currently, even if only one plot changes, all are updated. This can be optimized. In the case of this example, only 3 plots change, and with 3 plot layout there is no visible slowdown.

@mattpap You are right about the axis movement. Apologies for bringing noise to the issue

I can confirm that hardcoding the line (in my local installation) that @cutright said, works fast again for me as well (this.connect(this.force_paint, () => this.repaint()); to this.connect(this.force_paint, () => this.paint());).

Thanks @chesucr for looking into this too

I finally have some sample code as well. I believe @chesucr is seeing the same thing, but the issue is exacerbated with large layouts. Perhaps the following will suffice as test code, or something similar?

Personally, I don't mind the initial load time of large layouts.

In this test code, the only widget that does anything is the button, which updates all source data. In bokeh 0.12.15 this is slow on the first click, but happens fairly quickly on all subsequent clicks. Dragging plot data shows a lot of lag, preventing the user from interacting with any other object. Unlike the source data update, this lag doesn't go away after the first update.

from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Slider, TableColumn
from bokeh.plotting import figure
from bokeh.io import curdoc
from bokeh.models.widgets import Select, Button, Div, DataTable, Panel, Tabs,\
    RadioButtonGroup, TextInput, RadioGroup, CheckboxButtonGroup, Dropdown, CheckboxGroup
import numpy as np

N = 1000
tab_count = 10

sources = []
for i in range(tab_count):
    sources.append(ColumnDataSource(data=dict(x=np.random.random(size=N),
                                              y=np.random.random(size=N))))


def update_data():
    for j in range(tab_count):
        sources[j].data = {'x': np.random.random(size=N).tolist(),
                           'y': np.random.random(size=N).tolist()}


plots, layouts, buttons = [], [], []
for i in range(tab_count):
    plots.append(figure(width=400, height=400, tools='pan,wheel_zoom', output_backend='webgl', ))
    plots[i].circle(x='x', y='y', source=sources[i])
    buttons.append(Button(label="Button", width=100))
    buttons[i].on_click(update_data)
    layouts.append(column(row(plots[i]),
                          row(Select(value='1', options=['1', '2'], width=50, title="Select %s" % i),
                              buttons[i]),
                          row(Div(text="<b>This is a Div for Tab%s</b>" % i, width=1000)),
                          row(DataTable(source=sources[i], columns=[TableColumn(field="x", title="X"),
                                                                    TableColumn(field="y", title="Y")])),
                          row(RadioButtonGroup(labels=["Opt1", "Opt2"], active=0, width=100),
                              TextInput(value='', title="Text Input:", width=180)),
                          row(RadioGroup(labels=["RadioGroup 1", "RadioGroup 2"], active=0)),
                          row(CheckboxButtonGroup(labels=['Check1', 'Check2', 'Check3'], active=[0])),
                          row(Dropdown(label="Dropdown", menu=[('1', 'Dropdown1'), ('2', 'Dropdown2')], width=100)),
                          row(CheckboxGroup(labels=['Check1', 'Check2', 'Check3'], active=[0])),
                          row(Slider(start=1, end=100, value=10, step=1, title="Slider"))))


tabs = [Panel(child=layouts[i], title='Tab %s' % i) for i in range(tab_count)]
curdoc().add_root(Tabs(tabs=tabs)) 

I've finally updated my code to be compatible with the latest bokeh master (i.e., support for the Selection class and removal of row_header), and I can confirm there are no other lag issues beyond the repaint().

Issue persists in bokeh 0.12.16

Yes, when we are able to look into it, there will be updates here or in a PR.

As far as I can confirm, both examples here work well now with #8085