Michał, that's quite a bit more straightforward, nice :) Also made me realize that I should've used delays in synced-memoize.
Den tisdagen den 2:e september 2014 kl. 10:09:47 UTC+2 skrev Michał Marczyk: > > java.util.concurrent.ConcurrentHashMap has a putIfAbsent method that > could be used with delays to support this use case with a no > recomputation guarantee: > > (def chm (java.util.concurrent.ConcurrentHashMap.)) > > ;; this will work as expected whether chm has a value for :foo or not > (let [d (delay (+ 1 2))] > ;; NB. the two @d expressions refer to different ds > (if-let [d (.putIfAbsent chm :foo d)] @d @d)) > ;= 3 > > ;; this will remove all entries > (.clear chm) > > No point switching away from Atoms if occasional recomputation is ok > and they perform well enough, of course (and whether this would be an > improvement performance-wise in any given scenario would need to be > determined by benchmarking). > > > On 2 September 2014 00:54, Marcus Magnusson <mar...@gmail.com > <javascript:>> wrote: > > Forget about sleep, there's work to be done! I realized that the > drawback I > > mentioned with my solution, where concurrent calls to the memoized > function > > with different sets of arguments would be handled one-by-one, could > easily > > be resolved by having the cache inside synced-memoize store futures for > > calculating the value, rather than the value itself - some small changes > and > > everything should hopefully work well (don't ask me about overhead, > though!) > > > > > > (defn synced-memoize [f] > > (let [mem (atom {}) > > handle-ch (chan)] > > (go-loop [] > > (let [[args ret-ch] (<! handle-ch)] > > (>! ret-ch (if-let [e (find @mem args)] > > (val e) > > (let [ret (future (apply f args))] > > (swap! mem assoc args ret) > > ret))) > > (recur))) > > (fn [& args] > > (if-let [e (find @mem args)] > > (deref (val e)) > > (let [ret (chan)] > > (>!! handle-ch [args ret]) > > (deref (<!! ret))))))) > > > > (def lookup > > (let [calc-value* (synced-memoize calc-value)] > > (fn [cache key] > > (if (contains? @cache key) > > (@cache key) > > ((swap! cache assoc key (calc-value* key)) key))))) > > > > > > Den måndagen den 1:e september 2014 kl. 23:55:58 UTC+2 skrev Marcus > > Magnusson: > >> > >> Huh, I gave it some more thought - of course, the reason why memoize > won't > >> help us here is that there is no synchronization between simultaneous > >> invocations with the same set of arguments. This is typically not a > problem > >> if the underlying function is fast, but in our case it would be neat > with an > >> alternative. I got the idea of writing a channel-based version of > memoize, > >> which will ensure that invoking the memoized function simultaneously > with > >> the same arguments will only call the underlying function once. Below > is a > >> quick implementation with one obvious drawback (and probably tons of > bugs > >> and points of improvement!), namely that if the underlying function is > >> costly, and we invoke the memoized function with a bunch of different > >> non-cached set of arguments, then calculating the values will be done > >> one-by-one. This could be fixed for example by having handle-ch > delegate to > >> another channel, where we have one channel per set of arguments - I'll > leave > >> that for someone else, or for when I've had some sleep and realized > what a > >> bad idea it was... :) Note that I haven't worked much with core.async, > and > >> that there is probably a much more straightforward solution (for > example the > >> one given by Thomas Heller), so please let me know of any issues with > this: > >> > >> > >> (defn synced-memoize [f] > >> (let [mem (atom {}) > >> handle-ch (chan)] > >> (go-loop [] > >> (let [[args ret-ch] (<! handle-ch)] > >> (>! ret-ch (if-let [e (find @mem args)] > >> (val e) > >> (let [ret (apply f args)] > >> (swap! mem assoc args ret) > >> ret))) > >> (recur))) > >> (fn [& args] > >> (if-let [e (find @mem args)] > >> (val e) > >> (let [ret (chan)] > >> (>!! handle-ch [args ret]) > >> (<!! ret)))))) > >> > >> (def lookup > >> (let [calc-value* (synced-memoize calc-value)] > >> (fn [cache key] > >> (if (contains? @cache key) > >> (@cache key) > >> (swap! cache assoc key (calc-value* key)))))) > >> > >> > >> > >> Den måndagen den 1:e september 2014 kl. 22:35:56 UTC+2 skrev Marcus > >> Magnusson: > >>> > >>> I reckon if that worked, there would be no need for memoize anyway, > but I > >>> don't think swap! will allow for it. I'm far from an expert on swap! > or > >>> atoms, but several swap!s may be run simultaneously on a single atom > (and > >>> swap! may re-run the swapping function if the atom has been changed > since > >>> the swapping function was invoked). In other words, if two swap!s are > >>> invoked at about the same time for the same key (which is not > currently in > >>> the cache), both invocations may be given the same value of the cache > at > >>> that moment, which will lead to the value being re-calculated in both > >>> invocations - and even if calc-value is memoized, the same thing may > occur > >>> in the memoized function. > >>> > >>> As I said, I'm far from an expert, so I wrote a small test that shows > >>> that calc-value may indeed be called more than once (core.async here > is > >>> simply to ensure that each invocation of calc-value is printed on its > own > >>> line): > >>> > >>> > >>> (def out-ch (chan 100)) > >>> > >>> (go-loop [] > >>> (println (<! out-ch)) > >>> (recur)) > >>> > >>> (defn calc-value [x] > >>> (put! out-ch x) > >>> (* x x)) > >>> > >>> (def cache (atom {})) > >>> > >>> (def lookup > >>> (let [calc-value* (memoize calc-value)] > >>> (fn [cache key] > >>> ((swap! cache (fn [cache'] > >>> (if (contains? cache' key) > >>> cache' > >>> (assoc cache' key (calc-value* key))))) > >>> key)))) > >>> > >>> > >>> => (dotimes [_ 1000] > >>> (future (lookup cache (rand-int 20)))) > >>> 11 > >>> 12 > >>> 16 > >>> 1 > >>> nil > >>> 6 > >>> 15 > >>> 17 > >>> 18 > >>> 14 > >>> 2 > >>> 5 > >>> 19 > >>> 10 > >>> 0 > >>> 7 > >>> 9 > >>> 4 > >>> 4 > >>> 13 > >>> 9 > >>> 13 > >>> > >>> > >>> Den måndagen den 1:e september 2014 kl. 21:32:01 UTC+2 skrev Alex > >>> Baranosky: > >>>> > >>>> I believe you can replace: > >>>> (when-not (contains? @cache k) > >>>> (swap! cache assoc k (calc-value* k)))) > >>>> > >>>> with: > >>>> (swap! cache (fn [cache'] > >>>> (if (contains? cache' k) > >>>> cache' > >>>> (assoc cache' k (calc-value* k))))) > >>>> > >>>> Note: I haven't run this code > >>>> > >>>> > >>>> On Mon, Sep 1, 2014 at 2:20 AM, Colin Fleming <colin.ma...@gmail.com> > > >>>> wrote: > >>>>> > >>>>> Hi Thomas, > >>>>> > >>>>> Normally I'd agree with you, but in my case it actually works quite > >>>>> well since I don't need to expire or worry about sizing. This is for > caching > >>>>> objects on IntelliJ parse tree elements, and the element with its > cached > >>>>> values is thrown away every time the document is parsed, so these > are pretty > >>>>> transient caches. In this particular case the calculation isn't too > >>>>> heavyweight either so recalculating it in the unlikely event of > contention > >>>>> isn't a big deal. In fact, this discussion and the related thinking > has made > >>>>> me realise that my original solution was actually sufficient for my > somewhat > >>>>> strange use case, which is nice :-). For more typical server side > caching, I > >>>>> agree that Guava would be a great solution. > >>>>> > >>>>> Cheers, > >>>>> Colin > >>>>> > >>>>> > >>>>> On 1 September 2014 20:54, Thomas Heller <th.h...@gmail.com> wrote: > >>>>>> > >>>>>> As much as I like Clojure and atoms, I do not think they are a good > >>>>>> fit for caching. Not only is it impossible to address the > concurrency issues > >>>>>> related to multiple threads loading the same object, but you also > have to do > >>>>>> expiration and size management yourself. Immutability doesn't help > much for > >>>>>> caching either. There is core.cache that does some bits but I > probably would > >>>>>> recommend using something like CacheBuilder from the guava libs: > >>>>>> > >>>>>> See > >>>>>> https://code.google.com/p/guava-libraries/ > >>>>>> > >>>>>> > http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilder.html > > >>>>>> > >>>>>> Its Java but the Clojure<->Java interop is so good that it doesn't > >>>>>> matter much. > >>>>>> > >>>>>> On Saturday, August 30, 2014 7:27:05 AM UTC+2, Colin Fleming wrote: > >>>>>>> > >>>>>>> Hi all, > >>>>>>> > >>>>>>> I want to use a map to cache values based on a key. I'm planning > to > >>>>>>> use an atom for this. My basic operation is "give me the value for > this key" > >>>>>>> - if the value exists in the map then that value should be > returned, > >>>>>>> otherwise a new value should be calculated, inserted in the map > and then > >>>>>>> returned. My plan is to implement something like the following: > >>>>>>> > >>>>>>> > >>>>>>> (defn ensure [cache key] > >>>>>>> (if (contains? cache key) > >>>>>>> cache > >>>>>>> (assoc cache key (calc-value key)))) > >>>>>>> > >>>>>>> (let [value (get (swap! cache ensure key) key)] > >>>>>>> ... do my thing with value ...) > >>>>>>> > >>>>>>> > >>>>>>> So 'ensure' ensures that the cache contains the value for key, the > >>>>>>> swap! operation returns the cache with the value and then I get it > out. This > >>>>>>> works but feels a little clumsy, is there a better way to do this? > >>>>>>> > >>>>>>> Also, looking at the Atom source code, I see that this will cause > a > >>>>>>> CAS operation even if the value returned from swap! is identical > to the > >>>>>>> original value. It seems like a reasonable optimisation would be > to check if > >>>>>>> the values are identical and not update if so - is there a reason > this might > >>>>>>> not be a good idea? > >>>>>>> > >>>>>>> Thanks, > >>>>>>> Colin > >>>>>> > >>>>>> -- > >>>>>> You received this message because you are subscribed to the Google > >>>>>> Groups "Clojure" group. > >>>>>> To post to this group, send email to clo...@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+u...@googlegroups.com > >>>>>> For more options, visit this group at > >>>>>> http://groups.google.com/group/clojure?hl=en > >>>>>> --- > >>>>>> You received this message because you are subscribed to the Google > >>>>>> Groups "Clojure" group. > >>>>>> To unsubscribe from this group and stop receiving emails from it, > send > >>>>>> an email to clojure+u...@googlegroups.com. > >>>>>> For more options, visit https://groups.google.com/d/optout. > >>>>> > >>>>> > >>>>> -- > >>>>> You received this message because you are subscribed to the Google > >>>>> Groups "Clojure" group. > >>>>> To post to this group, send email to clo...@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+u...@googlegroups.com > >>>>> For more options, visit this group at > >>>>> http://groups.google.com/group/clojure?hl=en > >>>>> --- > >>>>> You received this message because you are subscribed to the Google > >>>>> Groups "Clojure" group. > >>>>> To unsubscribe from this group and stop receiving emails from it, > send > >>>>> an email to clojure+u...@googlegroups.com. > >>>>> For more options, visit https://groups.google.com/d/optout. > >>>> > >>>> > > -- > > You received this message because you are subscribed to the Google > > Groups "Clojure" group. > > To post to this group, send email to clo...@googlegroups.com > <javascript:> > > Note that posts from new members are moderated - please be patient with > your > > first post. > > To unsubscribe from this group, send email to > > clojure+u...@googlegroups.com <javascript:> > > For more options, visit this group at > > http://groups.google.com/group/clojure?hl=en > > --- > > You received this message because you are subscribed to the Google > Groups > > "Clojure" group. > > To unsubscribe from this group and stop receiving emails from it, send > an > > email to clojure+u...@googlegroups.com <javascript:>. > > For more options, visit https://groups.google.com/d/optout. > -- 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 --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.