1- # Licensed under the Apache License, Version 2.0 (the "License");
2- # you may not use this file except in compliance with the License.
3- # See the License at http://www.apache.org/licenses/LICENSE-2.0
4- # Distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
1+ # distutils: language = c++
2+ # cython: language_level=3
3+ # cython: nonecheck=False
4+ # cython: cdivision=True
5+ # cython: initializedcheck=False
6+ # cython: infer_types=True
7+ # cython: wraparound=False
8+ # cython: boundscheck=False
59
610"""
711LRU-K evicts the morsel whose K-th most recent access is furthest in the past. Note, the
3034the BufferPool implements limit to only evict up to 32 items per 'transaction'
3135"""
3236
33- import heapq
34- import time
37+ import heapq as py_heapq
38+
39+ from libc.stdint cimport int64_t
3540from collections import defaultdict
41+ from time import monotonic_ns
42+
43+ cdef class LRU_K:
44+
45+ __slots__ = (" k" , " slots" , " access_history" , " removed" , " heap" ,
46+ " hits" , " misses" , " evictions" , " inserts" , " size" )
3647
48+ cdef public int64_t k
49+ cdef dict slots
50+ cdef object access_history
51+ cdef set removed
52+ cdef list heap
3753
38- class LRU2 :
39- def __init__ (self , k = 2 ):
54+ cdef int64_t hits
55+ cdef int64_t misses
56+ cdef int64_t evictions
57+ cdef int64_t inserts
58+ cdef public int64_t size
59+
60+ def __cinit__ (self , int64_t k = 2 ):
4061 self .k = k
4162 self .slots = {}
4263 self .access_history = defaultdict(list )
@@ -47,70 +68,72 @@ def __init__(self, k=2):
4768 self .misses = 0
4869 self .evictions = 0
4970 self .inserts = 0
50-
5171 self .size = 0
5272
5373 def __len__ (self ):
5474 return len (self .slots)
5575
56- def get (self , key : bytes ) :
57- value = self .slots .get (key )
76+ def get (self , bytes key ) -> Optional[ bytes] :
77+ cdef object value = self .slots.get(key)
5878 if value is not None:
5979 self.hits += 1
6080 self._update_access_history(key )
6181 else:
6282 self.misses += 1
6383 return value
6484
65- def set (self , key : bytes , value ):
85+ def set(self , bytes key , bytes value ):
6686 self .inserts += 1
6787 if key not in self .slots:
6888 self .size += 1
6989 self .slots[key] = value
7090 self ._update_access_history(key)
7191 return None
7292
73- def _update_access_history (self , key : bytes ):
74- access_time = time .monotonic_ns ()
75- if len (self .access_history [key ]) == self .k :
76- old_entry = self .access_history [key ].pop (0 )
93+ cdef void _update_access_history(self , bytes key):
94+ cdef int64_t access_time = monotonic_ns()
95+ cdef list history = self .access_history[key]
96+ if len (history) == self .k:
97+ old_entry = history.pop(0 )
7798 self .removed.add(old_entry)
78- self .access_history [key ].append ((access_time , key ))
79- heapq .heappush (self .heap , (access_time , key ))
80-
81- def evict (self , details = False ):
99+ history.append((access_time, key))
100+ py_heapq.heappush(self .heap, (access_time, key))
101+
102+ def evict (self , bint details = False ):
103+ cdef int64_t _oldest_access_time
104+ cdef bytes oldest_key
105+ cdef int64_t new_access_time
106+ cdef tuple popped
82107 while self .heap:
83- oldest_access_time , oldest_key = heapq .heappop (self .heap )
84- if (oldest_access_time , oldest_key ) in self .removed :
85- self .removed .remove ((oldest_access_time , oldest_key ))
108+ popped = py_heapq.heappop(self .heap)
109+ _oldest_access_time, oldest_key = popped
110+ if popped in self .removed:
111+ self .removed.remove(popped)
86112 continue
87113
88114 if len (self .access_history[oldest_key]) == 1 :
89115 # Synthetic access to give a grace period
90- new_access_time = time . monotonic_ns ()
116+ new_access_time = monotonic_ns()
91117 self .access_history[oldest_key].append((new_access_time, oldest_key))
92- heapq .heappush (self .heap , (new_access_time , oldest_key ))
118+ py_heapq .heappush(self .heap, (new_access_time, oldest_key))
93119 continue
94120
95- # Evict the key with the oldest k-th access
96121 if oldest_key not in self .slots:
97122 continue
123+
98124 value = self .slots.pop(oldest_key)
99125 self .access_history.pop(oldest_key)
100126 self .size -= 1
101127 self .evictions += 1
102- if details : # pragma: no cover
128+ if details:
103129 return oldest_key, value
104130 return oldest_key
105131
106- if details : # pragma: no cover
107- return None , None # No item was evicted
132+ if details:
133+ return None , None
108134 return None
109135
110- def delete (self , key : bytes ):
111- """
112- Delete an item from the cache.
113- """
136+ def delete (self , bytes key ):
114137 if key in self .slots:
115138 self .slots.pop(key, None )
116139 self .access_history.pop(key, None )
@@ -127,11 +150,11 @@ def keys(self):
127150 def stats (self ):
128151 return self .hits, self .misses, self .evictions, self .inserts
129152
130- def reset (self , reset_stats = False ):
131- self .slots = {}
153+ def reset (self , bint reset_stats = False ):
154+ self .slots.clear()
132155 self .access_history.clear()
133156 self .removed.clear()
134- self .heap = []
157+ self .heap.clear()
135158 if reset_stats:
136159 self .hits = 0
137160 self .misses = 0
0 commit comments