zoukankan      html  css  js  c++  java
  • LRU(最近最少使用)(python实现)

    """
    python3 only
    LRU cache
    """
    from collections import OrderedDict
    from functools import wraps
    
    
    def fib(n):
        if n <= 1:  # 0 or 1
            return n
        return f(n - 1) + f(n - 2)  # 由于涉及到重复计算,这个递归函数在 n 大了以后会非常慢。 O(2^n)
    
    
    """
    下边就来写一个缓存装饰器来优化它。传统方法是用个数组记录之前计算过的值,但是这种方式不够 Pythonic
    """
    
    
    def cache(func):
        """先引入一个简单的装饰器缓存,其实原理很简单,就是内部用一个字典缓存已经计算过的结果"""
        store = {}
    
        @wraps(func)
        def _(n):   # 这里函数没啥意义就随便用下划线命名了
            if n in store:
                return store[n]
            else:
                res = func(n)
                store[n] = res
                return res
        return _
    
    
    @cache
    def f(n):
        if n <= 1:  # 0 or 1
            return n
        return f(n - 1) + f(n - 2)
    
    
    """
    问题来了,假如空间有限怎么办,我们不可能一直向缓存塞东西,当缓存达到一定个数之后,我们需要一种策略踢出一些元素,
    用来给新的元素腾出空间。
    一般缓存失效策略有
    - LRU(Least-Recently-Used): 替换掉最近请求最少的对象,实际中使用最广。cpu缓存淘汰和虚拟内存效果好,web应用欠佳
    - LFU(Least-Frequently-Used): 缓存污染问题(一个先前流行的缓存对象会在缓存中驻留很长时间)
    - First in First out(FIFO)
    - Random Cache: 随机选一个删除
    
    LRU 是常用的一个,比如 redis 就实现了这个策略,这里我们来模拟实现一个。
    要想实现一个 LRU,我们需要一种方式能够记录访问的顺序,并且每次访问之后我们要把最新使用到的元素放到最后(表示最新访问)。
    当容量满了以后,我们踢出最早访问的元素。假如用一个链表来表示的话:
    
    [1] -> [2] -> [3]
    
    假设最后边是最后访问的,当访问到一个元素以后,我们把它放到最后。当容量满了,我们踢出第一个元素就行了。
    一开始的想法可能是用一个链表来记录访问顺序,但是单链表有个问题就是如果访问了中间一个元素,我们需要拿掉它并且放到链表尾部。
    而单链表无法在O(1)的时间内删除一个节点(必须要先搜索到它),但是双端链表可以,因为一个节点记录了它的前后节点,
    只需要把要删除的节点的前后节点链接起来就行了。
    还有个问题是如何把删除后的节点放到链表尾部,如果是循环双端链表就可以啦,我们有个 root 节点链接了首位节点,
    只需要让 root 的前一个指向这个被删除节点,然后让之前的最后一个节点也指向它就行了。
    
    使用了循环双端链表之后,我们的操作就都是 O(1) 的了。这也就是使用一个 dict 和一个 循环双端链表 实现LRU 的思路。
    不过一般我们使用内置的 OrderedDict(原理和这个类似)就好了,要实现一个循环双端链表是一个不简单的事情。
    
    """
    
    
    class LRUCache:
        def __init__(self, capacity=128):
            self.capacity = capacity
            # 借助 OrderedDict 我们可以快速实现一个 LRUCache,OrderedDict 内部其实也是使用循环双端链表实现的
            # OrderedDict 有两个重要的函数用来实现 LRU,一个是 move_to_end,一个是 popitem,请自己看文档
            self.od = OrderedDict()
    
        def get(self, key, default=None):
            val = self.od.get(key, default)  # 如果没有返回 default,保持 dict 语义
            self.od.move_to_end(key)   # 每次访问就把key 放到最后表示最新访问
            return val
    
        def add_or_update(self, key, value):
            if key in self.od:  # update
                self.od[key] = value
                self.od.move_to_end(key)
            else:  # insert
                self.od[key] = value
                if len(self.od) > self.capacity:  # full
                    self.od.popitem(last=False)
    
        def __call__(self, func):
            """
            一个简单的 LRU 实现。有一些问题需要思考下:
    
            - 这里为了简化默认参数只有一个数字 n,假如可以传入多个参数,如何确定缓存的key 呢?
            - 这里实现没有考虑线程安全的问题,要如何才能实现线程安全的 LRU 呢?当然如果不是多线程环境下使用是不需要考虑的
            - 假如这里没有用内置的 dict,你能使用 redis 来实现这个 LRU 吗,如果使用了 redis,我们可以存储更多数据到服务器。而使用字典实际上是缓存了Python进程里(localCache)。
            - 这里只是实现了 lru 策略,你能同时实现一个超时 timeout 参数吗?比如像是memcache 实现的 lazy expiration 策略
            - LRU有个缺点就是,对于周期性的数据访问会导致命中率迅速下降,有一种优化是 LRU-K,访问了次数达到 k 次才会将数据放入缓存
            """
            def _(n):
                if n in self.od:
                    return self.get(n)
                else:
                    val = func(n)
                    self.add_or_update(n, val)
                    return val
            return _
    
    
    @LRUCache(10)
    def f_use_lru(n):
        if n <= 1:  # 0 or 1
            return n
        return f(n - 1) + f(n - 2)
    
    
    def test():
        import time
        beg = time.time()
        for i in range(34):
            print(f(i))
        print(time.time() - beg)
        beg = time.time()
        for i in range(34):
            print(f_use_lru(i))
        print(time.time() - beg)
    
    
    if __name__ == '__main__':
        test()
  • 相关阅读:
    【NumPy】 之常见运算(np.around、np.floor、np.ceil、np.where)(转)
    python3中collections模块(转)
    numpy.random模块用法小结
    生成模型和判别模型(《统计学习方法》截图)
    linux(Ubuntu)下机器学习/深度学习环境配置
    sklearn中predict_proba的用法例子(转)
    机器学习案例实战之信用卡欺诈检测——收获的技术
    分析长期治疗数据显示TNF拮抗剂似乎并未增加严重感染和肿瘤发生风险
    超声在鉴别诊断RA与PsA中的应用价值
    结合超声计数炎症关节的改良版DAS28的临床应用
  • 原文地址:https://www.cnblogs.com/muzinan110/p/11158608.html
Copyright © 2011-2022 走看看