"""
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()

LRU(最近最少使用)(python实现)的更多相关文章

  1. LRU算法的Python实现

    http://flychao88.iteye.com/blog/1977653文章中介绍了常见的几种缓存淘汰策略 LRU:least recently used,最近最少使用算法.其实就是按使用时间倒 ...

  2. LRU最近最少使用算法

    最近最少使用算法有两种实现方式: (1)记时法:对于每一页增设一个访问时间计时器.每当一个页面被访问时,当时的绝对时钟内容被复制到对应的访问时间计时器中.这样系统就记录了内存中所有页面最后一次被访问的 ...

  3. LRU(最近最少使用淘汰算法)基本实现

     LRU(Least Recently Used) 出发点:在页式存储管理中,如果一页很长时间未被访问,则它在最近一段时间内也不会被访问,即时间局部性,那我们就把它调出(置换出)内存. 为了实现LRU ...

  4. java 自定义 LRU(最近最少使用)策略 实现 缓存机制

    1. java提供了一个简单的方式实现LRU:  LinkedHashMap   2. 自定义实现 LRU至少需要两个主要操作: 添加(add)和搜索(search) public class LRU ...

  5. LRU算法原理解析

    LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的. 现代操作系统提供了一种对主存的抽象概念虚拟内存,来对主存进行更好地管理.他将主存 ...

  6. LeetCode--146--LRU缓存机制(python)

    运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key) - 如果密钥 (key) 存 ...

  7. 手撕LRU缓存了解一下

    面试官:来了,老弟,LRU缓存实现一下? 我:直接LinkedHashMap就好了. 面试官:不要用现有的实现,自己实现一个. 我:..... 面试官:回去等消息吧.... 大家好,我是程序员学长,今 ...

  8. 手撕LRU缓存

    面试官:来了,老弟,LRU缓存实现一下? 我:直接LinkedHashMap就好了. 面试官:不要用现有的实现,自己实现一个. 我:..... 面试官:回去等消息吧.... 大家好,我是程序员学长,今 ...

  9. 数据库存取缓冲区的LRU与MRU算法

    数据库存取缓冲区的LRU与MRU算法 1.Cache Hit and Cache Miss 当使用者第一次向数据库发出查询数据的请求的时候,数据库会先在缓冲区中查找该数据,如果要访问的数据恰好已经在缓 ...

随机推荐

  1. 图解JavaScript闭包面试题

    由于最近在学习关于闭包相关的知识,并且闭包这个知识点让我有点搞不太清楚其具体的定义,所以在网上也查阅了很多大佬的讲解和对闭包的一个定义. 最后感觉还是MDN上的说法感觉比较好理解一些,对闭包还是不太理 ...

  2. Actor模型的状态(State)+行为(Behavior)+邮箱(Mailbox)

    状态(State)+行为(Behavior)+邮箱(Mailbox) 基于Actor模型的CQRS.ES解决方案分享 开场白 大家晚上好,我是郑承良,跟大家分享的话题是<基于Actor模型的CQ ...

  3. Spring Security 官网文档学习

    文章目录 通过`maven`向普通的`WEB`项目中引入`spring security` 配置 `spring security` `configure(HttpSecurity)` 方法 自定义U ...

  4. [C++] 习题 2.15 实现简单环形队列

    目录 前置技能 环形队列 具体实现 设计一个环形队列,用front和rear分别作为队头和队尾指针,另外用一个tag表示队列是空 ( 0 ) 还是不空 ( 1 ),这样就可以用front==rear作 ...

  5. Linux基础-05-正文处理、tar、解压缩

    1. 使用cat命令进行文件的纵向合并 1) 使用cat命令实现文件的纵向合并: a) 例如:使用cat命令将baby.age.baby.kg和baby.sex这三个文件纵向合并为baby文件的方法: ...

  6. ps 指令

    ps显示系统当前进程信息, ps 存在多个版本,因此 ps options 的种类繁多.这里只列举平时开发过程中常用的命令,如果有错误或者更好的例子.烦请在评论区指出 语法 ps [options] ...

  7. Mysql union和union all用法

    1: 什么时候用union和union all ?    我们经常会碰到这样的应用,两个表的数据按照一定的查询条件查询出来以后,需要将结果合并到一起显示出来,这个时候 就需要用到union和union ...

  8. 在论坛中出现的比较难的sql问题:16(取一个字段中的数字)

    原文:在论坛中出现的比较难的sql问题:16(取一个字段中的数字) 所以,觉得有必要记录下来,这样以后再次碰到这类问题,也能从中获取解答的思路. 问题:取一个字段中的数字http://bbs.csdn ...

  9. ef core数据迁移的一点小感悟

    ef core在针对mysql数据迁移的时候,有些时候没法迁移...有两种情况没法迁移,一种是因为efcore的bug问题导致没法迁移,这个在github上有个问题集,另外一种是对数据表进行较大幅度的 ...

  10. C#避免WinForm窗体假死

    WinForm窗体在使用过程中如果因为程序等待时间太久而导致窗体本身假死无法控制,会严重影响用户的体验,这种情况大多是UI线程被耗时长的代码操作占用所致,可以新开一个线程用来完成耗时长的操作,然后再将 ...