Arc Forumnew | comments | leaders | submitlogin
Two recent discoveries: diskvar, defcache
6 points by akkartik 5280 days ago | 5 comments
I've found I reinvented the wheel (and poorly) on a couple of features without knowing they already existed. Neither is in the tutorial or the docs at arcfn, so here they are:

1. diskvar - easy persistence. Declare the file to save a var to with diskvar, and you can then todisk it to save.

2. defcache - defmemo with periodic recomputation.



2 points by palsecam 5280 days ago | link

Thanks, akkartik, for sharing this with us. I'm sure you're not the only one to not know about these 2 features, and anyway, always good to see someone discovering something.

About 1. There is also 'disktable, same principe but for a table.

Not in vanilla, see also (self-plug) 'ptable: http://arclanguage.org/item?id=10664, which is transparent-persistence (no need to call 'todisk).

About 2. I have a draft for a "better" 'defcache, one that would work with any number of args. Here it is, if anyone is interested. I needed it for caching calls to `stat'.

  (def out-from (cmd . cargs)
    (tostring:system (apply + (cons cmd cargs))))

  (def fstat (fname)
    (tokens:out-from "stat --terse " fname))  ; --terse POSIX? works on Linux at least

  (def cachr (timefn valfn)  ; timefn takes an argument: the value returned by (valfn args).
    (with (cache  (table)
      	   nilsym (uniq))
      (list cache
        (fn args
          (= args (copy args))  ; Arc bug: confusion w/ () and nil-terminated lists as table keys, scheme-impl related
          (unless (aand cache.args (< (since it!time) (timefn it)))
            (= cache.args (obj time (seconds)
	     	               val  (aif (apply valfn args) it nilsym))))
          (let v cache.args!val
            (if (isnt v nilsym) v))))))

  ; nilsym is used to differenciate nil == value not in
  ; table, and nil == result of (valf arg) is nil but 
  ; it's in the cache. Not quite sure if correct.
  ;
  ;  idea: make table work like this:
  ;     (w/uniq (missym)
  ;       (if (isnt (tbl key missym) missym)  
  ;	     "key is in tbl, and may be nil"
  ;	     "key is not in tbl"))
  ;   with missym being nil by default. Like 'read/eof.
  ;
  ;   I don't know if a hashtable can easily do this.
  ;   Need a 'delete-key fn or to modify 'wipe.
  ;   Maybe unatural that (= hash!key nil) doesn't 
  ;   delete. Less simple anyway.
  ;
  ;   Alt: encapsulate the table values in a list.

  ; Cache is made accessible, because:

  (def invalidate (key cach)  (= cach.key!time -1))

  (def reset (key cach f)
    (invalidate key cach)
    (f key))

  (let (h f) (cachr (fn (entry) 5) fstat)
    (= fstat-cache* h
         fstatc	  f))

  ; (reset '("/some/file") fstat-cache* fstatc)

  ; remove fstat-cache* entries that don't have been accessed recently. 
  ; we can always re-read from the disk, but RAM is precious.
  ; better: a FIFO mecanism, set max n of elems. memcached

  (new-bgthread 'fstat-cache-reaper 
    (fn ()  (each (k v) fstat-cache*
              (if (< (since v!time) (* 3 60 60))
                  (wipe fstat-cache*.k))))
    (* 30 60))

   (def mtime (fname)  (int ((fstatc fname) 12)))
   ...

-----

2 points by akkartik 5280 days ago | link

Very cool.

The nilsym is an interesting idea. Why do you prefer it to the nilcache approach in memo?

I liked seeing the invalidate/reset functions; one problem with the current defmemo and defcache is that a cache can grow unboundedly as stale values accumulate. In your case you could actually set cach.key to nilsym to avoid that. I've been thinking about more global policies than deleting individual keys.

Idea #1: Caches have a max size and when it is exceeded you delete the least recently used (LRU) element. Was this what you meant by the FIFO mechanism above? If you have a systems background as I do, this is the first approach you think of. (http://en.wikipedia.org/wiki/Cache_algorithms)

Idea #2: A function to clear the cache. I like this approach because it's simpler than watching what was accessed when, and it fits naturally to the way we do things.

  (defcache parse-fragment A fragment
    ..)

  (defcache check-fragment A fragment
    ..)

  (whilet (document-fragments doc)
    (clearcache A)
    ... ;; lots of stuff happens here every iteration, relying on fragment operations being cached.
I've added a notion of multiple caches; defcache specifies a cache, and clearcache simply clears everything in a cache regardless of which function it's for. Helps group related functions together.

-----

1 point by palsecam 5280 days ago | link

1. nilsym over nilcache: because I prefer to have only one table to store all the cached values. And I suppose it makes the code shorter. And maybe more efficient. And it's a new approach, and I like new things.

2. In your case you could actually set cach.key to nilsym to avoid that. I suppose you mean "set cach.key to (the real) nil (to actually delete the entry)"

3. I've been thinking about more global policies than deleting individual keys. Me too. As I said, this is just a draft. For instance, '(fstat)-cache-reaper is a common pattern, it needs to be automated, you should not write it manually.

4. delete the least recently used (LRU) element. Was this what you meant by the FIFO mechanism above? Yes.

5. A function to clear the cache. Yes, good idea. See above, as I said, ...-cache-reaper is a common pattern.

-----

2 points by akkartik 5279 days ago | link

Ah, I totally missed your reaper thread when I wrote http://arclanguage.org/item?id=10699

I suppose you mean "set cach.key to (the real) nil"

Yep :}

-----

2 points by kens 5274 days ago | link

New features such as diskvar, defcache, datestring, memory, positions, and many others are listed in my article "What's new in Arc 3": http://arcfn.com/2009/06/whats-new-in-arc3.html

Unfortunately I haven't updated the full documentation to include Arc 3 features.

-----