/kashae

A Calculation caching Nim library

Primary LanguageNimMIT LicenseMIT

Kashae - Yep, it's just cache

Kashae is a small implementation of caching return values for speeding up repetitive operations with the same parameters.

How to use

After installing kashae, simply annotate your procedure with {.cache.} to enable an unclearable "unlimited" cache. For instance with recursive Fibonacci number calculator:

proc fib(n: int): int {.cache.} =
  if n <= 1:
    result = n
  else:
    result = fib(n - 1) + fib(n - 2)

But shucks, what if we want to clear that pesky cache? Well this can be done using cacheOpt:. One way is to do:

proc fib(n: int): int {.cacheOpt: {clearParam}.}
# This is now expanded to 
proc fib(n: int, clearCache = false): int 

In the above example you can now do fib(10, true) and it'd run foo's logic after clearing the cache, rebuilding it as it goes. Secondly you can do:

proc fib(n: int): int {.cacheOpt: {clearFunc}.}

And now clearCache() can be called inside the function when you want to clear the cache based off some conditional like if the sun is in your eyes. If you start running out of ram and cannot find a reliable source to download it, you may want to consider limiting the cache size. Which can be done in the following ways:

proc fib(n: int): int {.cacheOpt: CacheOptions(flags: {clearParam}, size: 10).} # Hey we want options
proc fib(n: int): int {.cacheOpt: 10.} # Freewill does not exist

Now you might be scratching your head thinking "but why?", two words, well one root word twice, speedy speed. Even with a cleared cache you can vastly outperform the equivalent without a cache due to calculations being in the table, my timing of the above fib without cache, clearing the cache, and with a built cache are as follows:

name ............................... min time      avg time    std dv   runs
Un-kashaed Fib: 45 .............. 6970.467 ms   7014.019 ms   ±32.373    x10
Clearing Kashaed Fib: 45 ........... 0.002 ms      0.003 ms    ±0.002    x10
Kashaed Fib: 45 .................... 0.000 ms      0.000 ms    ±0.000    x10

Now stop writing a wrapper for that procedure in another module! We have magic in the way of cacheProc. Simply do the following to cache a prewritten procedrure.

proc someProcedureSomewhereElse(a, b: int): int # The procedure you want to cache
let cachedVersion = cacheProc(someProcedureSomewhereElse)
discard cachedVersion(100, 200) # now we call this

If the procedure is one that calls itself only the first call is cached. Cache proc has three variants, one that takes an int to change the cache size, one that takes a set[CacheOption] and finally one that takes a CacheOptions. Those overloads work identically to the cacheOpt ones, but only clearParam and clearCacheAfterRun work.

Implementation details

Presently Kashae by default uses a Ordered Table removing the oldest value first, which is beneficial for operations like the fib example since you have many branches that call values below it.

If you want to use a different type for the cache you can! It just requires matching the following concept signature.

  Cacheable*[K: Hash, V] = concept var c
    var a: Hash
    c.hasKey(a) is bool
    c[a] is V
    c[a] = V
    c.uncache()
    c.clear()

# If you prefer the Nim proc definitions:

  proc hasKey*[K, V](col: YourCollection, v: V): bool
  proc `[]`[K, V](col: YourCollection, k: K): V
  proc `[]=`[K, V](col: var YourCollection, k: K, v: V)
  proc uncache(col: var YourCollection)
  proc clear(col: var YourCollection)

After implementing all of those you can simply do setCurrentCache YourCollection[Hash, int], int can be replaced with any type you desire, but is needed to match the concept.