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

Cache refactor #829

Merged
merged 3 commits into from Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/cache.rst
Expand Up @@ -43,6 +43,9 @@ Please refer to the `joblib.Memory` `documentation
<https://pythonhosted.org/joblib/memory.html#memory-reference>`_ for a detailed explanation of these
parameters.

As of 0.7, librosa's cache wraps (rather than extends) the `joblib.Memory` object.
The memory object can be directly accessed by `librosa.cache.memory`.


Cache levels
------------
Expand Down
2 changes: 1 addition & 1 deletion librosa/__init__.py
Expand Up @@ -8,7 +8,7 @@
from .version import show_versions

# And all the librosa sub-modules
from . import cache
from ._cache import cache
from . import core
from . import beat
from . import decompose
Expand Down
42 changes: 29 additions & 13 deletions librosa/cache.py → librosa/_cache.py
Expand Up @@ -3,20 +3,24 @@
"""Function caching"""

import os
import sys
from joblib import Memory


class CacheManager(Memory):
'''The librosa cache manager class extends joblib.Memory
class CacheManager(object):
'''The librosa cache manager class wraps joblib.Memory
with a __call__ attribute, so that it may act as a function.

This allows us to override the librosa.cache module's __call__
field, thereby allowing librosa.cache to act as a decorator function.
Additionally, it provides a caching level filter, so that
different functions can be cached or not depending on the user's
preference for speed vs. storage usage.
'''

def __init__(self, location, level=10, **kwargs):
super(CacheManager, self).__init__(location, **kwargs)
def __init__(self, *args, **kwargs):

level = kwargs.pop('level', 10)

# Initialize the memory object
self.memory = Memory(*args, **kwargs)
# The level parameter controls which data we cache
# smaller numbers mean less caching
self.level = level
Expand Down Expand Up @@ -46,20 +50,32 @@ def decorator_apply(dec, func):
func, 'return decorated(%(signature)s)',
dict(decorated=dec(func)), __wrapped__=func)

if self.location is not None and self.level >= level:
return decorator_apply(self.cache, function)
if self.memory.location is not None and self.level >= level:
return decorator_apply(self.memory.cache, function)

else:
return function
return wrapper

def clear(self, *args, **kwargs):
return self.memory.clear(*args, **kwargs)

def eval(self, *args, **kwargs):
return self.memory.eval(*args, **kwargs)

def format(self, *args, **kwargs):
return self.memory.format(*args, **kwargs)

def reduce_size(self, *args, **kwargs):
return self.memory.reduce_size(*args, **kwargs)

def warn(self, *args, **kwargs):
return self.memory.warn(*args, **kwargs)


# Instantiate the cache from the environment
CACHE = CacheManager(os.environ.get('LIBROSA_CACHE_DIR', None),
cache = CacheManager(os.environ.get('LIBROSA_CACHE_DIR', None),
mmap_mode=os.environ.get('LIBROSA_CACHE_MMAP', None),
compress=os.environ.get('LIBROSA_CACHE_COMPRESS', False),
verbose=int(os.environ.get('LIBROSA_CACHE_VERBOSE', 0)),
level=int(os.environ.get('LIBROSA_CACHE_LEVEL', 10)))

# Override the module's __call__ attribute
sys.modules[__name__] = CACHE
2 changes: 1 addition & 1 deletion librosa/beat.py
Expand Up @@ -13,7 +13,7 @@
import numpy as np
import scipy

from . import cache
from ._cache import cache
from . import core
from . import onset
from . import util
Expand Down
2 changes: 1 addition & 1 deletion librosa/core/audio.py
Expand Up @@ -12,7 +12,7 @@

from .fft import get_fftlib
from .time_frequency import frames_to_samples, time_to_samples
from .. import cache
from .._cache import cache
from .. import util
from ..util.exceptions import ParameterError

Expand Down
2 changes: 1 addition & 1 deletion librosa/core/constantq.py
Expand Up @@ -12,7 +12,7 @@
from .time_frequency import cqt_frequencies, note_to_hz
from .spectrum import stft
from .pitch import estimate_tuning
from .. import cache
from .._cache import cache
from .. import filters
from .. import util
from ..util.exceptions import ParameterError
Expand Down
2 changes: 1 addition & 1 deletion librosa/core/pitch.py
Expand Up @@ -7,7 +7,7 @@

from .spectrum import _spectrogram
from . import time_frequency
from .. import cache
from .._cache import cache
from .. import util

__all__ = ['estimate_tuning', 'pitch_tuning', 'piptrack']
Expand Down
2 changes: 1 addition & 1 deletion librosa/core/spectrum.py
Expand Up @@ -15,7 +15,7 @@
from . import time_frequency
from .fft import get_fftlib
from .audio import resample
from .. import cache
from .._cache import cache
from .. import util
from ..util.exceptions import ParameterError
from ..filters import get_window, semitone_filterbank
Expand Down
2 changes: 1 addition & 1 deletion librosa/decompose.py
Expand Up @@ -19,7 +19,7 @@
import sklearn.decomposition

from . import core
from . import cache
from ._cache import cache
from . import segment
from . import util
from .util.exceptions import ParameterError
Expand Down
2 changes: 1 addition & 1 deletion librosa/feature/utils.py
Expand Up @@ -6,7 +6,7 @@
import numpy as np
import scipy.signal

from .. import cache
from .._cache import cache
from ..util.exceptions import ParameterError
from ..util.deprecation import Deprecated
__all__ = ['delta', 'stack_memory']
Expand Down
2 changes: 1 addition & 1 deletion librosa/filters.py
Expand Up @@ -50,7 +50,7 @@

from numba import jit

from . import cache
from ._cache import cache
from . import util
from .util.exceptions import ParameterError
from .util.decorators import deprecated
Expand Down
2 changes: 1 addition & 1 deletion librosa/onset.py
Expand Up @@ -15,7 +15,7 @@
import numpy as np
import scipy

from . import cache
from ._cache import cache
from . import core
from . import util
from .util.exceptions import ParameterError
Expand Down
4 changes: 2 additions & 2 deletions librosa/segment.py
Expand Up @@ -34,7 +34,7 @@
import sklearn.feature_extraction
import sklearn.neighbors

from . import cache
from ._cache import cache
from . import util
from .util.exceptions import ParameterError

Expand Down Expand Up @@ -692,7 +692,7 @@ def agglomerative(data, k, clusterer=None, axis=-1):
# Instantiate the clustering object
clusterer = sklearn.cluster.AgglomerativeClustering(n_clusters=k,
connectivity=grid,
memory=cache)
memory=cache.memory)

# Fit the model
clusterer.fit(data)
Expand Down
2 changes: 1 addition & 1 deletion librosa/util/utils.py
Expand Up @@ -9,7 +9,7 @@
import numpy as np
from numpy.lib.stride_tricks import as_strided

from .. import cache
from .._cache import cache
from .exceptions import ParameterError

# Constrain STFT block sizes to 256 KB
Expand Down
23 changes: 9 additions & 14 deletions tests/test_cache.py
Expand Up @@ -11,6 +11,7 @@
import numpy as np

import pytest
import librosa._cache


# Disable any initial cache settings
Expand All @@ -24,9 +25,8 @@
@pytest.fixture
def local_cache():
cache_dir = tempfile.mkdtemp()
os.environ['LIBROSA_CACHE_DIR'] = cache_dir
yield
os.environ.pop('LIBROSA_CACHE_DIR')
cache = librosa._cache.CacheManager(cache_dir, verbose=0, level=10)
yield cache
shutil.rmtree(cache_dir)


Expand All @@ -37,26 +37,21 @@ def func(x):

def test_cache_disabled():

os.environ.pop('LIBROSA_CACHE_DIR', None)
sys.modules.pop('librosa.cache', None)
import librosa.cache

func_cache = librosa.cache(level=-10)(func)

# When there's no cache directory in the environment,
# librosa.cache is a no-op.
cache = librosa._cache.CacheManager(None, verbose=0, level=10)
func_cache = cache(level=-10)(func)

assert func == func_cache


def test_cache_enabled(local_cache):

sys.modules.pop('librosa.cache', None)
import librosa.cache
librosa.cache.clear()
local_cache.clear()

func_cache = librosa.cache(level=-10)(func)
func_cache = local_cache(level=-10)(func)

# The cache should be active now
# The cache should be active now, so func_cache should be a different object from func
assert func_cache != func

# issue three calls to func
Expand Down