性能#

参考:Performance | Dash for Python Documentation | Plotly

本章包含了一些改进你的 Dash 应用程序性能的建议。

Dash 应用程序的主要性能限制可能是应用程序代码本身的回调。如果你能加快回调的速度,你的应用程序会感觉更轻快。

记忆化#

因为 Dash 的回调本质上是函数性的(它们不包含任何状态),所以很容易添加记忆缓存。内存保存函数被调用后的结果,并在使用相同参数调用函数时重用结果。

关于在 Dash 应用中使用记忆来提高性能的简单例子,请参阅高级回调章节中的“用记忆提高性能”一节。

Dash 应用程序经常跨多个进程或线程部署。在这些情况下,每个进程或线程包含自己的内存,它不跨实例共享内存。这意味着如果我们使用 lru_cache,缓存的结果可能不会在会话之间共享。

相反,我们可以使用 Flask-Caching 库,它将结果保存在一个像 Redis 这样的共享内存数据库中,或者作为文件保存在你的文件系统中。Flask-Caching 还有其他一些不错的特性,比如基于时间的逾时(expiry)。如果你想每小时或每天更新你的数据(清除你的缓存),基于时间的逾时是有用的。

下面是一个使用 Redis 的 Flask-Caching 的例子:

import datetime
import os
from dash import dcc, html
from dash.dependencies import Input, Output
from flask_caching import Cache

from app import app
import dash
dash.register_page(__name__)
cache = Cache(app.server, config={
    # try 'filesystem' if you don't want to setup redis
    'CACHE_TYPE': 'redis',
    'CACHE_REDIS_URL': os.environ.get('REDIS_URL', '')
})
app.config.suppress_callback_exceptions = True

timeout = 20
layout = html.Div([
    html.Div(id='flask-cache-memoized-children'),
    dcc.RadioItems(
        id='flask-cache-memoized-dropdown',
        options=[
            {'label': 'Option {}'.format(i), 'value': 'Option {}'.format(i)}
            for i in range(1, 4)
        ],
        value='Option 1'
    ),
    html.Div('Results are cached for {} seconds'.format(timeout))
])


@app.callback(
    Output('flask-cache-memoized-children', 'children'),
    Input('flask-cache-memoized-dropdown', 'value'))
@cache.memoize(timeout=timeout)  # in seconds
def render(value):
    return 'Selected "{}" at "{}"'.format(
        value, datetime.datetime.now().strftime('%H:%M:%S')
    )

下面是一个缓存数据集而不是回调的例子。它使用 FileSystem 缓存,将缓存的结果保存到文件系统。

如果有一个数据集用于更新多个回调,那么这种方法很有效。

import datetime as dt
from dash import dcc, html
import numpy as np
import pandas as pd
from dash.dependencies import Input, Output
from flask_caching import Cache
from app import app
import dash
dash.register_page(__name__)
cache = Cache(app.server, config={
    'CACHE_TYPE': 'filesystem',
    'CACHE_DIR': 'cache-directory'
})

TIMEOUT = 60


@cache.memoize(timeout=TIMEOUT)
def query_data():
    # This could be an expensive data querying step
    df = pd.DataFrame(
        np.random.randint(0, 100, size=(100, 4)),
        columns=list('ABCD')
    )
    now = dt.datetime.now()
    df['time'] = [now - dt.timedelta(seconds=5*i) for i in range(100)]
    return df.to_json(date_format='iso', orient='split')


def dataframe():
    return pd.read_json(query_data(), orient='split')


layout = html.Div([
    html.Div(f'数据在最近 {TIMEOUT} 秒内更新'),
    dcc.Dropdown(
        id='live-dropdown',
        value='A',
        options=[{'label': i, 'value': i} for i in dataframe().columns]
    ),
    dcc.Graph(id='live-graph')
])


@app.callback(Output('live-graph', 'figure'),
              Input('live-dropdown', 'value'))
def update_live_graph(value):
    df = dataframe()
    now = dt.datetime.now()
    return {
        'data': [{
            'x': df['time'],
            'y': df[value],
            'line': {
                'width': 1,
                'color': '#0074D9',
                'shape': 'spline'
            }
        }],
        'layout': {
            # display the current position of now
            # this line will be between 0 and 60 seconds
            # away from the last datapoint
            'shapes': [{
                'type': 'line',
                'xref': 'x', 'x0': now, 'x1': now,
                'yref': 'paper', 'y0': 0, 'y1': 1,
                'line': {'color': 'darkgrey', 'width': 1}
            }],
            'annotations': [{
                'showarrow': False,
                'xref': 'x', 'x': now, 'xanchor': 'right',
                'yref': 'paper', 'y': 0.95, 'yanchor': 'top',
                'text': 'Current time ({}:{}:{})'.format(
                    now.hour, now.minute, now.second),
                'bgcolor': 'rgba(255, 255, 255, 0.8)'
            }],
            # aesthetic options
            'margin': {'l': 40, 'b': 40, 'r': 20, 't': 10},
            'xaxis': {'showgrid': False, 'zeroline': False},
            'yaxis': {'showgrid': False, 'zeroline': False}
        }
    }

Graphs + Dash#

Plotly.js 开箱即用非常快。

大多数图形都是用 SVG 呈现的。这提供了清晰的渲染、发布质量的图像导出和广泛的浏览器支持。不幸的是,对于大型数据集(比如那些超过 15k 点的数据集),SVG 呈现图形可能很慢。为了克服这个限制, plotly.js 提供了一些图表类型的 WebGL 替代方案。WebGL 使用 GPU 渲染图形。

高性能的 WebGL 替代品包括:

  • scatterglscatter 图类型的 webgl 实现。Examples & reference

  • pointcloud:一个轻量级版本的 scattergl 具有有限的可定制性,但甚至更快的渲染。Reference

  • heatmapglheatmap 类型的 webgl 实现。Reference

:目前,dash 在更新时使用 plotly.jsnewPlot 回调重新绘制整个图形。通过在此逻辑中引入 restyle 回调,可以显著提高更新图表的性能。

Dash 客户端回调简介#

客户端回调以 JavaScript 在客户端执行代码,而不是以 Python 在服务器端执行代码。

更多关于客户端回调的信息,请参阅 客户端回调 一章。