LFU缓存在python中的实现

LFU缓存在python中的实现,python,caching,heap,priority-queue,Python,Caching,Heap,Priority Queue,我已经在python中实现了LFU缓存,并在 我在文章的最后给出了代码 但是我觉得代码有一些严重的问题: 1.为了给出一个场景,假设只有一个页面连续被访问(比如50次)。但这段代码将始终将已添加的节点标记为“已删除”,并再次将其添加到堆中。因此,基本上,同一页面将有50个不同的节点。因此大大增加了堆的大小。 2.这个问题几乎类似于第1季度的电话采访 这个人提到,与堆相比,双链表可以提供更好的效率。谁能解释一下,怎么解释 from llist import dllist import sys f

我已经在python中实现了LFU缓存,并在

我在文章的最后给出了代码

但是我觉得代码有一些严重的问题:
1.为了给出一个场景,假设只有一个页面连续被访问(比如50次)。但这段代码将始终将已添加的节点标记为“已删除”,并再次将其添加到堆中。因此,基本上,同一页面将有50个不同的节点。因此大大增加了堆的大小。
2.这个问题几乎类似于第1季度的电话采访 这个人提到,与堆相比,双链表可以提供更好的效率。谁能解释一下,怎么解释

from llist import dllist
import sys
from heapq import heappush, heappop

class LFUCache:
    heap = []
    cache_map = {}
    REMOVED = "<removed-task>"

    def __init__(self, cache_size):
        self.cache_size = cache_size

    def get_page_content(self, page_no):
        if self.cache_map.has_key(page_no):
            self.update_frequency_of_page_in_cache(page_no)  
        else:
            self.add_page_in_cache(page_no)

        return self.cache_map[page_no][2]       

    def add_page_in_cache(self, page_no):
        if (len(self.cache_map) == self.cache_size):
            self.delete_page_from_cache() 

        heap_node = [1, page_no, "content of page " + str(page_no)]
        heappush(self.heap, heap_node)
        self.cache_map[page_no] = heap_node

    def delete_page_from_cache(self):
        while self.heap:
            count, page_no, page_content = heappop(self.heap)
            if page_content is not self.REMOVED:
                del self.cache_map[page_no]
                return

    def update_frequency_of_page_in_cache(self, page_no): 
        heap_node = self.cache_map[page_no]
        heap_node[2] = self.REMOVED
        count = heap_node[0]

        heap_node = [count+1, page_no, "content of page " + str(page_no)]
        heappush(self.heap, heap_node)
        self.cache_map[page_no] = heap_node

def main():
    cache_size = int(raw_input("Enter cache size "))
    cache = LFUCache(cache_size)

    while 1:
        page_no = int(raw_input("Enter page no needed "))
        print cache.get_page_content(page_no)
        print cache.heap, cache.cache_map, "\n"


if __name__ == "__main__":
    main() 
从llist导入dllist
导入系统
从heapq导入heappush、heappop
LFUCache类:
堆=[]
缓存_映射={}
已删除=“”
定义初始化(自,缓存大小):
self.cache\u size=缓存大小
def获取页面内容(自身,页面编号):
如果self.cache\u map.具有\u键(页码):
缓存中页面的自我更新频率(页面编号)
其他:
在缓存中添加页面(页面号)
返回self.cache\u map[页码][2]
def在缓存中添加页面(自身,页面号):
如果(len(self.cache\u映射)=self.cache\u大小):
self.delete_page_from_cache()
堆节点=[1,页码,“页码内容”+str(页码)]
heappush(self.heap,heap\u节点)
self.cache\u映射[页面号]=堆节点
def从缓存中删除页面(自):
而self.heap:
计数,页码,页码=heapop(self.heap)
如果页面内容未自行删除:
del self.cache\u映射[页码]
返回
缓存中页面的def更新频率(自身,页面编号):
heap\u node=self.cache\u映射[页码]
heap_节点[2]=自删除
计数=堆\u节点[0]
堆节点=[计数+1,页码,“页码内容”+str(页码)]
heappush(self.heap,heap\u节点)
self.cache\u映射[页面号]=堆节点
def main():
缓存大小=int(原始输入(“输入缓存大小”))
cache=LFUCache(缓存大小)
而1:
页码=int(原始输入(“输入页码不需要”))
打印缓存。获取页面内容(页面编号)
打印cache.heap,cache.cache\u映射,“\n”
如果名称=“\uuuuu main\uuuuuuuu”:
main()

效率是一件棘手的事情。在实际应用中,使用最简单、最简单的算法通常是一个好主意,只有在速度明显较慢时才开始优化。然后通过进行分析来优化,找出代码运行缓慢的地方

如果您使用的是CPython,它会变得特别棘手,因为即使是用C实现的低效算法,由于常量因子很大,也可能会打败用Python实现的高效算法;e、 用Python实现的双链表往往比简单地使用普通Python列表慢得多,即使在理论上它应该更快的情况下也是如此

简单算法:

对于LFU,最简单的算法是使用字典将键映射到(项、频率)对象,并在每次访问时更新频率。这使得访问速度非常快(O(1)),但修剪缓存的速度较慢,因为需要按频率排序,以切断使用最少的元素。但是,对于某些使用特性,这实际上比其他“更智能”的解决方案要快

您可以针对这种模式进行优化,方法不是简单地将LFU缓存修剪到最大长度,而是在其增长过大时将其修剪到最大长度的50%。这意味着您的修剪操作很少被调用,因此与读取操作相比,它可能效率低下

使用堆:

在(1)中,您使用了堆,因为这是存储优先级队列的有效方法。但您没有实现优先级队列。生成的算法针对剪枝而不是访问进行了优化:您可以很容易地找到n个最小的元素,但如何更新现有元素的优先级并不那么明显。理论上,每次访问后都必须重新平衡堆,这是非常低效的

为了避免这种情况,您添加了一个技巧,即即使删除了元素,也要保留元素。但这是以空间换取时间

如果您不想在时间上进行交易,您可以就地更新频率,并在修剪缓存之前简单地重新平衡堆。您以较慢的修剪时间为代价重新获得快速访问时间,如上面的简单算法。(我怀疑两者之间是否存在速度差异,但我没有测量到这一点。)

使用双链接列表:

(2)中提到的双链接列表利用了此处可能发生的更改的性质:添加元素作为最低优先级(0次访问),或者将现有元素的优先级精确增加1。如果您按照以下方式设计数据结构,则可以充分利用这些属性:

您有一个按元素频率排序的双链接元素列表。此外,您还有一个字典,可以将项目映射到该列表中的元素

访问元素意味着:

  • 或者它不在字典中,也就是说,它是一个新项,在这种情况下,您可以简单地将它附加到双链接列表的末尾(O(1))
  • 或者它在字典中,在这种情况下,您增加元素中的频率,并在双链接列表中向左移动,直到列表再次排序(O(n)最坏情况,但通常更接近O(1))
要修剪缓存,只需从列表末尾(O(n))剪切n个元素