• Python——LRU_Cache


    funtools高阶函数——LRU_Cache

    源码: https://github.com/python/cpython/blob/3.10/Lib/functools.py

    def lru_cache(maxsize=128, typed=False):
        """Least-recently-used cache decorator.
        If *maxsize* is set to None, the LRU features are disabled and the cache
        can grow without bound.
        If *typed* is True, arguments of different types will be cached separately.
        For example, f(3.0) and f(3) will be treated as distinct calls with
        distinct results.
        Arguments to the cached function must be hashable.
        View the cache statistics named tuple (hits, misses, maxsize, currsize)
        with f.cache_info().  Clear the cache and statistics with f.cache_clear().
        Access the underlying function with f.__wrapped__.
        See:  https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
        """
    
        # Users should only access the lru_cache through its public API:
        #       cache_info, cache_clear, and f.__wrapped__
        # The internals of the lru_cache are encapsulated for thread safety and
        # to allow the implementation to change (including a possible C version).
    
        if isinstance(maxsize, int):
            # Negative maxsize is treated as 0
            if maxsize < 0:
                maxsize = 0
        elif callable(maxsize) and isinstance(typed, bool):
            # The user_function was passed in directly via the maxsize argument
            user_function, maxsize = maxsize, 128
            wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
            wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
            return update_wrapper(wrapper, user_function)
        elif maxsize is not None:
            raise TypeError(
                'Expected first argument to be an integer, a callable, or None')
    
        def decorating_function(user_function):
            wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
            wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
            return update_wrapper(wrapper, user_function)
    
        return decorating_function
    
    def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
        # Constants shared by all lru cache instances:
        sentinel = object()          # unique object used to signal cache misses
        make_key = _make_key         # build a key from the function arguments
        PREV, NEXT, KEY, RESULT = 0, 1, 2, 3   # names for the link fields
    
        cache = {}
        hits = misses = 0
        full = False
        cache_get = cache.get    # bound method to lookup a key or return None
        cache_len = cache.__len__  # get cache size without calling len()
        lock = RLock()           # because linkedlist updates aren't threadsafe
        root = []                # root of the circular doubly linked list
        root[:] = [root, root, None, None]     # initialize by pointing to self
    
        if maxsize == 0:
    
            def wrapper(*args, **kwds):
                # No caching -- just a statistics update
                nonlocal misses
                misses += 1
                result = user_function(*args, **kwds)
                return result
    
        elif maxsize is None:
    
            def wrapper(*args, **kwds):
                # Simple caching without ordering or size limit
                nonlocal hits, misses
                key = make_key(args, kwds, typed)
                result = cache_get(key, sentinel)
                if result is not sentinel:
                    hits += 1
                    return result
                misses += 1
                result = user_function(*args, **kwds)
                cache[key] = result
                return result
    
        else:
    
            def wrapper(*args, **kwds):
                # Size limited caching that tracks accesses by recency
                nonlocal root, hits, misses, full
                key = make_key(args, kwds, typed)
                with lock:
                    link = cache_get(key)
                    if link is not None:
                        # Move the link to the front of the circular queue
                        link_prev, link_next, _key, result = link
                        link_prev[NEXT] = link_next
                        link_next[PREV] = link_prev
                        last = root[PREV]
                        last[NEXT] = root[PREV] = link
                        link[PREV] = last
                        link[NEXT] = root
                        hits += 1
                        return result
                    misses += 1
                result = user_function(*args, **kwds)
                with lock:
                    if key in cache:
                        # Getting here means that this same key was added to the
                        # cache while the lock was released.  Since the link
                        # update is already done, we need only return the
                        # computed result and update the count of misses.
                        pass
                    elif full:
                        # Use the old root to store the new key and result.
                        oldroot = root
                        oldroot[KEY] = key
                        oldroot[RESULT] = result
                        # Empty the oldest link and make it the new root.
                        # Keep a reference to the old key and old result to
                        # prevent their ref counts from going to zero during the
                        # update. That will prevent potentially arbitrary object
                        # clean-up code (i.e. __del__) from running while we're
                        # still adjusting the links.
                        root = oldroot[NEXT]
                        oldkey = root[KEY]
                        oldresult = root[RESULT]
                        root[KEY] = root[RESULT] = None
                        # Now update the cache dictionary.
                        del cache[oldkey]
                        # Save the potentially reentrant cache[key] assignment
                        # for last, after the root and links have been put in
                        # a consistent state.
                        cache[key] = oldroot
                    else:
                        # Put result in a new link at the front of the queue.
                        last = root[PREV]
                        link = [last, root, key, result]
                        last[NEXT] = root[PREV] = cache[key] = link
                        # Use the cache_len bound method instead of the len() function
                        # which could potentially be wrapped in an lru_cache itself.
                        full = (cache_len() >= maxsize)
                return result
    
        def cache_info():
            """Report cache statistics"""
            with lock:
                return _CacheInfo(hits, misses, maxsize, cache_len())
    
        def cache_clear():
            """Clear the cache and cache statistics"""
            nonlocal hits, misses, full
            with lock:
                cache.clear()
                root[:] = [root, root, None, None]
                hits = misses = 0
                full = False
    
        wrapper.cache_info = cache_info
        wrapper.cache_clear = cache_clear
        return wrapper
    
    try:
        from _functools import _lru_cache_wrapper
    except ImportError:
        pass
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160

    使用循环队列和哈希表实现缓存。接受两个参数:

    • maxsize, 默认是128, 如果设置为None的话没有LRU特性
    • typed,默认是False, 如果设置为True的话,将明确区分参数,比如f(3.0)f(3)

    @functools.lru_cache(user_function)
    @functools.lru_cache(maxsize=128, typed=False)
    一个为函数提供缓存功能的装饰器,缓存 maxsize 组传入参数,在下次以相同参数调用时直接返回上一次的结果。用以节约高开销或I/O函数的调用时间。

    由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的。

    不同模式的参数可能被视为不同从而产生多个缓存项,例如, f(a=1, b=2) 和 f(b=2, a=1) 因其参数顺序不同,可能会被缓存两次。

    如果指定了 user_function,它必须是一个可调用对象。 这允许 lru_cache 装饰器被直接应用于一个用户自定义函数,让 maxsize 保持其默认值 128:

    @lru_cache
    def count_vowels(sentence):
        return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')
    
    • 1
    • 2
    • 3
    • 如果 maxsize 设为 None,LRU 特性将被禁用且缓存可无限增长。
    • 如果 typed 被设置为 true ,不同类型的函数参数将被分别缓存。 如果 typed 为 false ,实现通常会将它们视为等价的调用,只缓存一个结果。(有些类型,如 str 和 int ,即使 typed 为 false ,也可能被分开缓存)。

    为了帮助衡量缓存的有效性以及调整 maxsize 形参,被包装的函数会带有一个 cache_info() 函数,它返回一个 named tuple 以显示 hits, misses, maxsize 和 currsize。

    该装饰器也提供了一个用于清理/使缓存失效的函数 cache_clear()

    原始的未经装饰的函数可以通过 __wrapped__ 属性访问。它可以用于检查、绕过缓存,或使用不同的缓存再次装饰原始函数。

    缓存会保持对参数的引用并返回值,直到它们结束生命期退出缓存或者直到缓存被清空。

    LRU(最久未使用算法)缓存 在最近的调用是即将到来的调用的最佳预测值时性能最好(例如,新闻服务器上最热门文章倾向于每天更改)。 缓存的大小限制可确保缓存不会在长期运行进程如网站服务器上无限制地增长。

    一般来说,LRU缓存只在当你想要重用之前计算的结果时使用。因此,用它缓存具有副作用的函数、需要在每次调用时创建不同、易变的对象的函数或者诸如time或random之类的不纯函数是没有意义的。

    以下是使用缓存通过 动态规划 计算 斐波那契数列 的例子。

    from functools import lru_cache
    import time
      
      
    # Function that computes Fibonacci 
    # numbers without lru_cache
    def fib_without_cache(n):
        if n < 2:
            return n
        return fib_without_cache(n-1) + fib_without_cache(n-2)
          
    # Execution start time
    begin = time.time()
    fib_without_cache(30)
      
    # Execution end time
    end = time.time()
      
    print("Time taken to execute the\
    function without lru_cache is", end-begin)
    
    # Function that computes Fibonacci
    # numbers with lru_cache
    @lru_cache(maxsize = 128)
    def fib_with_cache(n):
        if n < 2:
            return n
        return fib_with_cache(n-1) + fib_with_cache(n-2)
          
    begin = time.time()
    fib_with_cache(30)
    end = time.time()
      
    print("Time taken to execute the \
    function with lru_cache is", end-begin)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    输出:

    Time taken to execute the function without lru_cache is 0.4448213577270508
    Time taken to execute the function with lru_cache is 2.8371810913085938e-05
    
    • 1
    • 2

    可以看到使用lru_cache能快很多

    参考

  • 相关阅读:
    UDP协议
    LLM - 使用 Ollama + OpenWebUI 在 Linux 服务器中高效部署大语言模型
    聊聊SpringBootTest的webEnvironment
    SpringBoot的流浪宠物系统
    最新 vie-vite框架下 jtopo安装使用
    fastjson 1.2.47 远程命令执行漏洞
    JavaWeb——IDEA相关配置(Tomcat安装)
    智能合约漏洞案例,DEI 漏洞复现
    T31开发笔记: 移动侦测
    openpyxl: Value must be either numerical or a string containing a wildcard
  • 原文地址:https://blog.csdn.net/uncle_ll/article/details/126209333