import panel as pn
pn.extension('terminal')
When developing applications that are to be used by multiple users and which may process a lot of data it is important to ensure the application is well optimized. Additionally complex applications may have very complex callbacks which are difficult to trace and debug. In this user guide section we will walk you some of the best practices to debug your applications and profile your application to maximize performance.
The Panel architecture ensures that multiple user sessions can run in the same process and therefore have access to the same global state. This means that we can cache data in Panel's global state
object, either by directly assigning to the pn.state.cache
dictionary object or by using the pn.state.as_cached
helper function.
To assign to the cache manually, simply put the data load or expensive calculation in an if
/else
block which checks whether the custom key is already present:
if 'data' in pn.state.cache:
data = pn.state.cache['data']
else:
pn.state.cache['data'] = data = ... # Load some data or perform an expensive computation
The as_cached
helper function on the other hand allows providing a custom key and a function and automatically caching the return value. If provided the args
and kwargs
will also be hashed making it easy to cache (or memoize) on the arguments to the function:
def load_data(*args, **kwargs):
return ... # Load some data
data = pn.state.as_cached('data', load_data, *args, **kwargs)
The first time the app is loaded the data will be cached and subsequent sessions will simply look up the data in the cache, speeding up the process of rendering. If you want to warm up the cache before the first user visits the application you can also provide the --warm
argument to the panel serve
command, which will ensure the application is initialized once on launch.
The /admin
panel provides an overview of the current application and provides tools for debugging and profiling. It can be enabled by passing the --admin
argument to the panel serve
command.
The overview page provides some details about currently active sessions, running versions and resource usage (if psutil
is installed).
The launch profiler profiles the execution time of the initialization of a particular application. It can be enabled by setting a profiler using the commandline --profiler
option. Available profilers include:
pyinstrument
: A statistical profiler with nice visual outputsnakeviz
: SnakeViz is a browser based graphical viewer for the output of Python’s cProfile module and an alternative to using the standard library pstats module.Once enabled the launch profiler will profile each application separately and provide the profiler output generated by the selected profiling engine.
In addition to profiling the launch step of an application it is often also important to get insight into the interactive performance of an application. For that reason Panel also provides the pn.io.profile
decorator that can be added to any callback and will report the profiling results in the /admin
panel. The profile
helper takes to arguments, the name to record the profiling results under and the profiling engine
to use.
@pn.io.profile('clustering', engine='snakeviz')
def get_clustering(event):
# some expensive calculation
...
widget.param.watch(my_callback, 'value')
The user profiling may also be used in an interactive session, e.g. we might decorate a simple callback with the profile
decorator:
import time
slider = pn.widgets.FloatSlider(name='Test')
@pn.depends(slider)
@pn.io.profile('formatting')
def format_value(value):
time.sleep(1)
return f'Value: {value+1}'
pn.Row(slider, format_value)
Then we can request the named profile 'formatting' using the pn.state.get_profile
function:
pn.state.get_profile('formatting')
The Logs page provides a detailed breakdown of the user interaction with the application. Additionally users may also log to this logger using the pn.state.log
function, e.g. in this example we log the arguments to the clustering function:
def get_clusters(x, y, n_clusters):
pn.state.log(f'clustering {x!r} vs {y!r} into {n_clusters} clusters.')
...
return ...
The logging terminal may also be used interactively, however you have to ensure that the 'terminal' extension is loaded with pn.extension('terminal')
. If the extension is initialized it can be rendered by accessing it on pn.state.log_terminal
:
slider = pn.widgets.FloatSlider(name='Test')
@pn.depends(slider)
def format_value(value):
pn.state.log(f'formatting value {value}')
return f'Value: {value+1}'
pn.Column(
pn.Row(slider, format_value),
pn.state.log_terminal,
sizing_mode='stretch_both'
)