首页 > 解决方案 > 散景:jupyter 实验室中的重复绘图会增加(浏览器)内存使用量

问题描述

我正在使用Bokeh在Jupyter Lab Notebook中绘制许多时间序列 (>100) 和许多点 (~20,000) 。在Jupyter 中多次执行单元格时,Chrome 每次运行的内存消耗会增加超过 400mb。在多次单元执行后,Chrome 往往会崩溃,通常是在累积了几 GB 的 RAM 使用量时。此外,每次执行后绘图往往会变慢。

Jupyter 中的“清除 [所有] 输出”“重新启动内核并清除所有输出...”也不会释放任何内存。在经典的Jupyter Notebook以及FirefoxEdge中,也会出现此问题。

我的 .ipynp 的最小版本:

import numpy as np
from bokeh.io import show, output_notebook
from bokeh.plotting import figure
import bokeh
output_notebook() # See e.g.: https://github.com/bokeh/bokeh-notebooks/blob/master/tutorial/01%20-%20Basic%20Plotting.ipynb
# Just create a list of numpy arrays with random-walks as dataset
ts_length = 20000
n_lines = 100
np.random.seed(0)
dataset = [np.cumsum(np.random.randn(ts_length)) + i*100 for i in range(n_lines)]
# Plot exactly the same linechart every time
plot = figure(x_axis_type="linear")
for data in dataset:
    plot.line(x=range(ts_length), y=data)
show(plot)

即使我每次在重新执行上面的(绘图)单元之前都执行以下单元,这种“内存泄漏”行为仍在继续:

bokeh.io.curdoc().clear()  
bokeh.io.state.State().reset()
bokeh.io.reset_output()

output_notebook() # has to be done again because output was reset


Bokeh 中是否有我可能忽略的其他机制,可以让我清理绘图并释放内存(在浏览器/js/客户端中)?

我是否必须在 Jupyter Notebook 中以其他方式绘制(或显示情节)以避免此问题?或者这仅仅是 Bokeh/Jupyter 的错误?


我的系统上安装的版本(Windows 10):

  • Python 3.6.6:Anaconda 自定义(64 位)
  • 散景:1.4.0
  • 铬:78.0.3904.108
  • 朱皮特:
    • 核心:4.6.1
    • 实验室:1.1.4
    • ipywidgets:7.5.1
    • 实验室扩展:
      • @bokeh/jupyter_bokeh:v1.1.1
      • @jupyter-widgets/jupyterlab-manager:v1.0.*

标签: pythonjupyter-notebookjupyterbokehjupyter-lab

解决方案


TLDR;这可能值得.

内存使用情况

只是关于不同方面的一些注释:

清除/重置功能

首先要注意,这些:

bokeh.io.curdoc().clear()  
bokeh.io.state.State().reset()
bokeh.io.reset_output()

仅影响Python 进程中的数据结构(例如 Jupyter Kernel)。它们永远不会对浏览器内存使用或占用空间产生任何影响。

一次性内存占用

仅基于数据,我预计大约在 64MB 附近:

20000 * 100 * 2 * 2  * 8 = 64MB

即:100 行,20k (x,y) 点,也将转换为 (sx,sy) 屏幕坐标,全部在 float64 (8byte) 类型数组中。但是,Bokeh 还为所有数据构建了空间索引,以支持诸如悬停工具之类的东西。我希望你用这些数据来炸毁这个索引。可能值得将此功能配置为可配置,这样不需要命中测试的人就不必为此付费。讨论此问题的功能请求问题将是合适的。

重复执行

应该有 DOM 事件触发器将在重新执行笔记本单元格时进行清理。也许这些已经坏了?不幸的是,在一个小团队中维护三个大型混合 Python/JS 工具(包括经典 Notebook)之间的集成是一个持续的挑战。错误报告问题将是适当的,以便可以跟踪和调查。

其他选项

你现在能做什么?

更优化的使用

至少对于您在此处具有相同长度的时间序列的特定情况,上述代码的结构非常不理想。您应该尝试将所有内容都放在一个中ColumnDataSource

ts_length = 20000
n_lines = 100
np.random.seed(0)

source = ColumnDataSource(data=dict(x=np.arange(ts_length)))
for i in range(n_lines):
    source.data[f"y{i}"] = np.cumsum(np.random.randn(ts_length)) + i*100

plot = figure()
for i in range(n_lines):
    plot.line(x='x', y=f"y{i}", source=source)
show(plot)

通过将序列文字传递给line,您的代码会创建 99 个不必要的 CDS 对象(每次line调用一个)。也没有重复使用x数据,导致不必要地向 BokehJS 发送 99*20k 额外点。通过发送一个普通列表而不是一个 numpy 数组,这些也都使用效率较低(在时间和空间上)的默认 JSON 编码进行编码,而不是可用于 numpy 数组的有效二进制编码。

也就是说,这并没有引起这里的所有问题,并且可能不是单独的解决方案。但我想确保指出这一点。

数据着色器

对于这么多点,您可以考虑将DataShader与 Bokeh 结合使用。Holoviews库还在高级别的自动集成了 Bokeh 和 Datashader 通过在 Python 端预渲染图像,Datashader 实际上是一种带宽压缩工具(除其他外)。

PNG导出

Bokeh 倾向于权衡提供各种交互性。但是,如果您实际上并不需要这种交互性,那么您需要支付一些额外的费用。如果这是您的情况,您可以考虑生成静态 PNG:

from bokeh.io.export import get_screenshot_as_png
p = get_screenshot_as_png(plot)

在此处输入图像描述

您需要安装导出绘图中列出的其他可选依赖项,如果您正在绘制许多绘图,您可能需要考虑为每个调用显式保存和重用 Web 驱动程序。


推荐阅读