Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ec0e910

Browse files
committed
Improve the memory utilization (and speed) of functools.lru_cache().
1 parent 2a1e74a commit ec0e910

2 files changed

Lines changed: 34 additions & 21 deletions

File tree

Lib/functools.py

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial']
1313

1414
from _functools import partial, reduce
15-
from collections import OrderedDict, namedtuple
15+
from collections import namedtuple
1616
try:
1717
from _thread import allocate_lock as Lock
1818
except:
@@ -147,17 +147,20 @@ def lru_cache(maxsize=100, typed=False):
147147
# to allow the implementation to change (including a possible C version).
148148

149149
def decorating_function(user_function,
150-
*, tuple=tuple, sorted=sorted, map=map, len=len, type=type, KeyError=KeyError):
150+
*, tuple=tuple, sorted=sorted, map=map, len=len, type=type):
151151

152+
cache = dict()
152153
hits = misses = 0
154+
cache_get = cache.get # bound method for fast lookup
153155
kwd_mark = (object(),) # separates positional and keyword args
154-
lock = Lock() # needed because OrderedDict isn't threadsafe
156+
lock = Lock() # needed because linkedlist isn't threadsafe
157+
root = [] # root of circular doubly linked list
158+
root[:] = [root, root, None, None] # initialize by pointing to self
155159

156160
if maxsize is None:
157-
cache = dict() # simple cache without ordering or size limit
158-
159161
@wraps(user_function)
160162
def wrapper(*args, **kwds):
163+
# simple caching without ordering or size limit
161164
nonlocal hits, misses
162165
key = args
163166
if kwds:
@@ -167,23 +170,18 @@ def wrapper(*args, **kwds):
167170
key += tuple(map(type, args))
168171
if kwds:
169172
key += tuple(type(v) for k, v in sorted_items)
170-
try:
171-
result = cache[key]
173+
result = cache_get(key)
174+
if result is not None:
172175
hits += 1
173176
return result
174-
except KeyError:
175-
pass
176177
result = user_function(*args, **kwds)
177178
cache[key] = result
178179
misses += 1
179180
return result
180181
else:
181-
cache = OrderedDict() # ordered least recent to most recent
182-
cache_popitem = cache.popitem
183-
cache_renew = cache.move_to_end
184-
185182
@wraps(user_function)
186183
def wrapper(*args, **kwds):
184+
# size limited caching that tracks accesses by recency
187185
nonlocal hits, misses
188186
key = args
189187
if kwds:
@@ -193,20 +191,33 @@ def wrapper(*args, **kwds):
193191
key += tuple(map(type, args))
194192
if kwds:
195193
key += tuple(type(v) for k, v in sorted_items)
194+
PREV, NEXT = 0, 1 # names of link fields
196195
with lock:
197-
try:
198-
result = cache[key]
199-
cache_renew(key) # record recent use of this key
196+
link = cache_get(key)
197+
if link is not None:
198+
link = cache[key]
199+
# record recent use of the key by moving it to the front of the list
200+
link_prev, link_next, key, result = link
201+
link_prev[NEXT] = link_next
202+
link_next[PREV] = link_prev
203+
last = root[PREV]
204+
last[NEXT] = root[PREV] = link
205+
link[PREV] = last
206+
link[NEXT] = root
200207
hits += 1
201208
return result
202-
except KeyError:
203-
pass
204209
result = user_function(*args, **kwds)
205210
with lock:
206-
cache[key] = result # record recent use of this key
207-
misses += 1
211+
last = root[PREV]
212+
link = [last, root, key, result]
213+
cache[key] = last[NEXT] = root[PREV] = link
208214
if len(cache) > maxsize:
209-
cache_popitem(0) # purge least recently used cache entry
215+
# purge least recently used cache entry
216+
old_prev, old_next, old_key, old_result = root[NEXT]
217+
root[NEXT] = old_next
218+
old_next[PREV] = root
219+
del cache[old_key]
220+
misses += 1
210221
return result
211222

212223
def cache_info():

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Library
2626

2727
- Issue #11199: Fix the with urllib which hangs on particular ftp urls.
2828

29+
- Improve the memory utilization and speed of functools.lru_cache.
30+
2931
- Issue #14222: Use the new time.steady() function instead of time.time() for
3032
timeout in queue and threading modules to not be affected of system time
3133
update.

0 commit comments

Comments
 (0)