Repository: napari/napari

Issue:
Do not set the dask cache to 1/10th of RAM on import
## 🐛 Bug
Copy-pasting this over from a discussion in dask-image

@jni says https://github.com/dask/dask-image/issues/194#issuecomment-786450341
>  yes napari sets the dask cache to 1/10th of RAM on import. Yes I know it's bad, I've tried to argue to change that.

@tlambert03 https://github.com/dask/dask-image/issues/194#issuecomment-786611351
> Wait which part is bad? I don't remember any arguments about this (and is it really only 1/10 now?)

@jni
https://github.com/dask/dask-image/issues/194#issuecomment-786968555
> Well, it seems to me that modifying the behaviour of a popular third party library (dask) just by importing is undesirable. We see above that just importing napari altered the behaviour of dask during diagnostics. In general, imho there should be a visible function call to alter the dask caching behaviour.
> 
> At any rate, we discussed this in various group meetings, just never seriously. The basic issue I've run into is that the dask cache is good-ish when exploring, but bad when using "play", because you fill up the cache and then spend the whole time putting stuff into the cache queue and deleting it from the back of it.
> 
> > (and is it really only 1/10 now?)
> 
> [napari/napari#1666](https://github.com/napari/napari/pull/1666)

and then @tlambert03 says
> totally agree. let's change it!

## To Reproduce

https://github.com/dask/dask-image/issues/194 is an example of a problem popping up because of this



Discussion:
hi @GenevieveBuckley, can you summarize the crux of the problem?  I tried to read through that issue to see why the cache was causing that issue... but I didn't immediately see it.  I see that @jni lamented our cache, and I agree we should figure out how to offer caching without having global side effects... but I'm still missing why exactly dask/dask-image#194 popped up because of it?
...nope! I don't remember any of the conversations @jni talks about. I'm just kicking this over here so it doesn't get lost completely.

ok that's fine.  I just wanted to clarify (for my own understanding) whether the cache was actually _causing_ that problem, or just came up as a topic while investigating that problem (and possibly made investigating it harder), but didn't actually contribute to the problem.  @jni, do you know?
> ...or just came up as a topic while investigating that problem (and possibly made investigating it harder), but didn't actually contribute to the problem

I'd lean towards this probably being a more accurate summary. Feel free to edit the text above if you like.


@tlambert03 

Indeed, it just caused a red-herring, because calling `data.compute()` before adding the array to napari made the problem go away — but that's only because `dask.compute()` would cache the whole thing, so no more computation would need to happen. Discussion starts at this comment and the two that follow:

https://github.com/dask/dask-image/issues/194#issuecomment-786419669
gotcha.  thanks!

Resolving patch:
diff --git a/napari/utils/dask_utils.py b/napari/utils/dask_utils.py
--- a/napari/utils/dask_utils.py
+++ b/napari/utils/dask_utils.py
@@ -167,7 +167,6 @@ def configure_dask(data) -> Callable[[], ContextManager[dict]]:
     ...    data[0, 2].compute()
     """
     if _is_dask_data(data):
-        create_dask_cache()  # creates one if it doesn't exist
         if dask.__version__ < LooseVersion('2.15.0'):
             warnings.warn(
                 'For best performance with Dask arrays in napari, please '


Test patch:
diff --git a/napari/layers/_tests/test_dask_layers.py b/napari/layers/_tests/test_dask_layers.py
--- a/napari/layers/_tests/test_dask_layers.py
+++ b/napari/layers/_tests/test_dask_layers.py
@@ -9,8 +9,8 @@
 from napari import layers, utils, viewer
 
 
-def test_dask_array_creates_cache():
-    """Test that adding a dask array creates a dask cache and turns of fusion."""
+def test_dask_array_doesnt_create_cache():
+    """Test that dask arrays don't create cache but turns off fusion."""
     # by default we have no dask_cache and task fusion is active
     original = dask.config.get("optimization.fuse.active", None)
 
@@ -20,8 +20,9 @@ def mock_set_view_slice():
     layer = layers.Image(da.ones((100, 100)))
     layer._set_view_slice = mock_set_view_slice
     layer.set_view_slice()
-    # adding a dask array will turn on the cache, and turn off task fusion.
-    assert isinstance(utils.dask_cache, dask.cache.Cache)
+    # adding a dask array won't create cache, but will turn off task fusion,
+    # *but only* during slicing (see "mock_set_view_slice" above)
+    assert utils.dask_cache is None
     assert dask.config.get("optimization.fuse.active", None) == original
 
     # if the dask version is too low to remove task fusion, emit a warning
@@ -34,9 +35,8 @@ def mock_set_view_slice():
     dask.__version__ = _dask_ver
 
     # make sure we can resize the cache
-    assert utils.dask_cache.cache.total_bytes > 1000
-    utils.resize_dask_cache(1000)
-    assert utils.dask_cache.cache.total_bytes <= 1000
+    utils.resize_dask_cache(10000)
+    assert utils.dask_cache.cache.available_bytes == 10000
 
     # This should only affect dask arrays, and not numpy data
     def mock_set_view_slice2():
@@ -50,11 +50,12 @@ def mock_set_view_slice2():
     utils.dask_cache = None
 
 
-def test_list_of_dask_arrays_creates_cache():
+def test_list_of_dask_arrays_doesnt_create_cache():
     """Test that adding a list of dask array also creates a dask cache."""
+    utils.dask_cache = None  # in case other tests created it
     original = dask.config.get("optimization.fuse.active", None)
     _ = layers.Image([da.ones((100, 100)), da.ones((20, 20))])
-    assert isinstance(utils.dask_cache, dask.cache.Cache)
+    assert utils.dask_cache is None
     assert dask.config.get("optimization.fuse.active", None) == original
 
 
@@ -90,6 +91,10 @@ def get_array():
 def test_dask_optimized_slicing(delayed_dask_stack, monkeypatch):
     """Test that dask_configure reduces compute with dask stacks."""
 
+    # make sure we have a cache
+    # big enough for 10+ (10, 10, 10) "timepoints"
+    utils.resize_dask_cache(100000)
+
     # add dask stack to the viewer, making sure to pass multiscale and clims
     v = viewer.ViewerModel()
     dask_stack = delayed_dask_stack['stack']
@@ -168,13 +173,16 @@ def dask_optimized_slicing(*args, **kwds):
 @pytest.mark.sync_only
 def test_dask_cache_resizing(delayed_dask_stack):
     """Test that we can spin up, resize, and spin down the cache."""
+
+    # make sure we have a cache
+    # big enough for 10+ (10, 10, 10) "timepoints"
+    utils.resize_dask_cache(100000)
+
     # add dask stack to the viewer, making sure to pass multiscale and clims
-    utils.dask_cache = None
 
     v = viewer.ViewerModel()
     dask_stack = delayed_dask_stack['stack']
 
-    # adding a new stack should spin up a cache
     v.add_image(dask_stack, multiscale=False, contrast_limits=(0, 1))
     assert utils.dask_cache.cache.available_bytes > 0
     # make sure the cache actually has been populated
@@ -203,13 +211,6 @@ def test_dask_cache_resizing(delayed_dask_stack):
         v.dims.set_point(0, i)
     assert len(utils.dask_cache.cache.heap.heap) > 0
 
-    # however, if the dask_cache attribute is deleted entirely (or set to None)
-    # we will have no memory of it ever having been created.
-    # and adding a new stack will spin up a cache
-    del utils.dask_cache
-    v.add_image(dask_stack, multiscale=False, contrast_limits=(0, 1))
-    assert utils.dask_cache.cache.available_bytes > 0
-
 
 def test_prevent_dask_cache(delayed_dask_stack):
     """Test that pre-emptively setting cache to zero keeps it off"""