Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement cache function for memoization support #2411

Merged
merged 16 commits into from
Aug 13, 2022
43 changes: 33 additions & 10 deletions examples/user_guide/Performance_and_Debugging.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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.\n",
"\n",
"## Caching\n",
"\n",
"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.\n",
"Caching data and computation is one of the most effective ways to speed up your applications. Some common examples of scenarios that benefit from caching is working with large datasets that you have to load from disk or over a network connection or you have to perform expensive computations that don't depend on any extraneous state. Panel makes it easy for you to add caching to you applications using a few approaches. Panel' architecture is also very well suited towards caching since 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, using the `pn.state.as_cached` helper function or the `pn.cache` decorator. Once cached all current and subsequent sessions will be sped up by having access to the cache.\n",
"\n",
"### Manual usage\n",
"\n",
"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: \n",
"\n",
Expand All @@ -34,16 +31,42 @@
" pn.state.cache['data'] = data = ... # Load some data or perform an expensive computation\n",
"```\n",
"\n",
"### `pn.cache` decorator\n",
"\n",
"The `pn.cache` decorator provides an easy way to cache the outputs of a function depending on its inputs (i.e. `memoize`). If you've ever used the Python `@lru_cache` decorator you will be familiar with this concept. However the `pn.cache` functions supports additional cache `policy`'s apart from LRU (least-recently used), including `LFU` (least-frequently-used) and 'FIFO' (first-in-first-out). This means that if the specified number of `max_items` is reached Panel will automatically evict items from the cache based on this `policy`. Additionally items can be deleted from the cache based on a `ttl` (time-to-live) value given in seconds.\n",
"\n",
"#### Caching in memory\n",
"\n",
"The `pn.cache` decorator can easily be combined with the different Panel APIs including `pn.bind` and `pn.depends` providing a powerful way to speed up your applications.\n",
"\n",
"```python\n",
"@pn.cache(max_items=10, policy='LRU')\n",
"def load_data(path):\n",
" return ... # Load some data\n",
"```\n",
"\n",
"Once you have decorated your function with `pn.cache` any call to `load_data` will be cached in memory until `max_items` value is reached (i.e. you have loaded 10 different `path` values). At that point the `policy` will determine which item is evicted.\n",
"\n",
"#### Disk caching\n",
"\n",
"If you have `diskcache` installed you can also cache the results to disk by setting `to_disk=True`. The `diskcache` library will then cache the value to the supplied `cache_path` (defaulting to `./cache`). Making use of disk caching allows you to cache items even if the server is restarted.\n",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the support for diskcache

"\n",
"#### Clearing the cache\n",
"\n",
"Once a function has been decorated with `pn.cache` you can easily clear the cache by calling `.clear()` on that function, e.g. in the example above you could call `load_data.clear()`. If you want to clear all caches you may also call `pn.state.clear_caches()`.\n",
"\n",
"### `pn.state.as_cached`\n",
"\n",
"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: \n",
"\n",
"```python\n",
"def load_data(*args, **kwargs):\n",
" return ... # Load some data\n",
" return ... # Load some data\n",
"\n",
"data = pn.state.as_cached('data', load_data, *args, **kwargs)\n",
"```\n",
"\n",
"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 as soon as it is launched. If you want to populate the cache in a separate script from your main application you may also provide the path to a setup script using the `--setup` argument to `panel serve`. If you want to periodically update the cache look into the ability to [schedule tasks](Deploy_and_Export.ipynb#Scheduling-task-with-pn.state.schedule_task)."
"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 as soon as it is launched. If you want to populate the cache in a separate script from your main application you may also provide the path to a setup script using the `--setup` argument to `panel serve`. If you want to periodically update the cache look into the ability to [schedule tasks](Deploy_and_Export.ipynb#Scheduling-task-with-pn.state.schedule_task).\n"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me this looks like extensive and great documentation.

]
},
{
Expand Down
2 changes: 1 addition & 1 deletion panel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
from .depends import bind, depends # noqa
from .interact import interact # noqa
from .io import ( # noqa
_jupyter_server_extension_paths, ipywidget, serve, state,
_jupyter_server_extension_paths, cache, ipywidget, serve, state,
)
from .layout import ( # noqa
Accordion, Card, Column, FlexBox, GridBox, GridSpec, Row, Spacer, Tabs,
Expand Down
1 change: 1 addition & 0 deletions panel/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""
import sys

from .cache import cache # noqa
from .callbacks import PeriodicCallback # noqa
from .document import init_doc, unlocked, with_lock # noqa
from .embed import embed_state # noqa
Expand Down
Loading