Hello Laurent, On Mar 10, 11:45 am, Laurent PETIT <laurent.pe...@gmail.com> wrote:
> * usage of refs : I had a bad feeling, and cgrand confirmed this to > me by pointing an even more interesting counter-argument. Me: using > refs is not mandatory since you do not need to synchronize this change > with anything else. I don't think, that this is entirely true! You have to syncronise the cache with the state in the strategy. This can be done with atoms only if the cache and the strategy state are contained in the same atom and all updates happen in a single call to swap! (or reset!). As soon as you have multiple calls, you need transactions again, because the atom might change between the two calls. And you can't swap! and return a result at the same time. Say you have the LRU strategy. You deref the cache, find the result cached. Now you swap! in the new access time into your state. But between the deref and the swap! another call might trigger the removal of the entry. So you always have to guard against the atom suddenly changing underneath - even if it's only one atom. Something what annoys me more is that the computation may be fired of several times. Since the f is supposedly expensive, I'd rather avoid that. > * lookup function responsibilities: I cannot (yet) offer a better > way to approach the problem, but I have a bad feeling with the lookup > function changing things "behind the scene". Can you explain a little more on what you think is the problem? Things like LRU need some bookkeeping on access. Otherwise one just does not have the required information at hand and the strategy is not possible. So I think, you mean something else or you consider LRU a bad strategy. I have no experience with the different strategies, but I tend to believe the first possibility. If this is true you refer to the TTL strategy, where the lookup function is removing old entries. I also don't really like this. What are the possibilities? * removal in cache-update, may cause items to linger quite long in the cache depending on the call structure, possibly much longer than the TTL, still needs special handling in cache-lookup or out-of-date entries will be returned * removal in cache-lookup, happens (possibly) much more often * a dedicated thread, which is created when a new entry is done. Then sleeps for it's TTL and then wakes and removes the entry. This will however potentially create a lot of threads just sleeping around. I don't know how expensive this is. * a cron like thread, which sleeps to the next removal time, removes the out-of-date item for that point, sleeps again, and so on. This would be only one thread, but a more difficult implementation and requires cross-thread syncronisation. The cache-lookup provided the most bang for the buck if you want to keep it simple. > Christophe: And by using refs, you synchronize the > change with a potential uber STM transaction, and if this uber STM > transaction retries, you will not benefit from the memoization, since > the memoization itself will be discarded by the retry. This is a problem the STM cannot solve. We would need some dont-nest-me-dosync, which does not merge with a possible surrounding dosync. (This was actually part of the famous discussion between Rich and Cliff Click: How to define the borders of a transaction region?) I gave it another run and came up with another solution. Even more hairy. :P * a warm cache is fast * a cache miss is potentially slow, but the missing value is computed only once, multiple requests for the same item wait for the initially triggered computation * the cache can be updated (almost) in parallel for different items (defn memoize ([f] (memoize f naive-strategy)) ([f [cache-lookup cache-update]] (let [cache-state (atom {})] (fn [& args] (if-let [e (cache-lookup cache-state args)] @(val e) @(locking cache-state (if-let [e (cache-lookup cache-state args)] (val e) (let [result (promise)] (.start (Thread. #(deliver result (apply f args)))) (swap! cache-state assoc-in [:cache args] result) (cache-update cache-state args) result)))))))) But with this you can't reliably implement strategies like LRU and the like. But this is really an interesting problem. I'll mull a little more about it. :) Sincerely Meikel -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en