nimbus-eth1/nimbus/utils/lru_cache.nim

136 lines
4.3 KiB
Nim
Raw Normal View History

# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
## Hash as hash can: LRU cache
## ===========================
##
## provide last-recently-used cache mapper
const
# debugging, enable with: nim c -r -d:noisy:3 ...
noisy {.intdefine.}: int = 0
isMainOk {.used.} = noisy > 2
import
stew/results,
tables
export
results
type
LruKey*[T,K] = ## derive an LRU key from function argument
proc(arg: T): K {.gcsafe, raises: [Defect,CatchableError].}
LruValue*[T,V,E] = ## derive an LRU value from function argument
proc(arg: T): Result[V,E] {.gcsafe, raises: [Defect,CatchableError].}
LruCache*[T,K,V,E] = object
maxItems: int ## max number of entries
tab: OrderedTable[K,V] ## cache data table
toKey: LruKey[T,K]
toValue: LruValue[T,V,E]
{.push raises: [Defect,CatchableError].}
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc initLruCache*[T,K,V,E](cache: var LruCache[T,K,V,E];
toKey: LruKey[T,K], toValue: LruValue[T,V,E];
cacheMaxItems = 10) =
## Initialise new LRU cache
cache.maxItems = cacheMaxItems
cache.toKey = toKey
cache.toValue = toValue
# note: Starting from Nim v0.20, tables are initialized by default and it
# is not necessary to call initOrderedTable() function explicitly.
proc getLruItem*[T,K,V,E](cache: var LruCache[T,K,V,E]; arg: T): Result[V,E] =
## Return `toValue(arg)`, preferably from result cached earlier
let key = cache.toKey(arg)
# Get the cache if already generated, marking it as recently used
if cache.tab.hasKey(key):
let value = cache.tab[key]
# Pop and append at end. Note that according to manual, this
# costs O(n) => inefficient
cache.tab.del(key)
cache.tab[key] = value
return ok(value)
# Return unless OK
let rcValue = ? cache.toValue(arg)
# Limit mumer of cached items
if cache.maxItems <= cache.tab.len:
# Delete oldest/first entry
var tbd: K
# Kludge: OrderedTable[] still misses a proper API.
for key in cache.tab.keys:
# Tests suggest that deleting here also works in that particular case.
tbd = key
break
cache.tab.del(tbd)
# Add cache entry
cache.tab[key] = rcValue
result = ok(rcValue)
# ------------------------------------------------------------------------------
# Debugging/testing
# ------------------------------------------------------------------------------
when isMainModule and isMainOK:
import
sequtils
const
cacheLimit = 10
keyList = [
185, 208, 53, 54, 196, 189, 187, 117, 94, 29, 6, 173, 207, 45, 31,
208, 127, 106, 117, 49, 40, 171, 6, 94, 84, 60, 125, 87, 168, 183,
200, 155, 34, 27, 67, 107, 108, 223, 249, 4, 113, 9, 205, 100, 77,
224, 19, 196, 14, 83, 145, 154, 95, 56, 236, 97, 115, 140, 134, 97,
153, 167, 23, 17, 182, 116, 253, 32, 108, 148, 135, 169, 178, 124, 147,
231, 236, 174, 211, 247, 22, 118, 144, 224, 68, 124, 200, 92, 63, 183,
56, 107, 45, 180, 113, 233, 59, 246, 29, 212, 172, 161, 183, 207, 189,
56, 198, 130, 62, 28, 53, 122]
var
getKey: LruKey[int,int] =
proc(x: int): int = x
getValue: LruValue[int,string,int] =
proc(x: int): Result[string,int] = ok($x)
cache: LruCache[int,int,string,int]
cache.initLruCache(getKey, getValue, cacheLimit)
var lastQ: seq[int]
for w in keyList:
var
key = w mod 13
reSched = cache.tab.hasKey(key)
value = cache.getLruItem(key)
queue = toSeq(cache.tab.keys)
if reSched:
echo "+++ rotate ", value, " => ", queue
else:
echo "*** append ", value, " => ", queue
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------