Re: atoms, memoize, future-s and CAS

2014-12-20 Thread Andy L
Hi,

Thanks for all suggestions. It all encouraged me to deep dive into atom-s
code which turns out to be a simple wrapper over Java
java.util.concurrent.atomic.AtomicReference which essentially is a
spinlock. Knowing how it works under the hood makes so easier
to use it ...

Below piece (hopefully correct) let me update map atomically:
(defn cond-assoc!
  [atomic-map key val]
  (loop []
(let [candidate (if (contains? @atomic-map key) @atomic-map (assoc
@atomic-map key val))]
  (if (compare-and-set! atomic-map @atomic-map candidate)
candidate
(recur)


I also learned that using future (or essentially Java threads) is so poor
approach to scalability. derefing memoized delay of my function wrapped
with future worked well to some point. After pushing it a bit with more
concurrent tasks, my JVM started freezing under pressure of to many
threads. I ended up with my custom hybrid of a queued future-s. I have also
a version done in core.async - I will try to write it up in a separate
piece at some point ..

Best regards,
Andy

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-08 Thread Andy L


 Most of the cache implementations in core.cache have no side-effects. They
 simply return a new cache rather than overwriting the old one. The memoize
 library places the cache in an atom, so it's guaranteed to change
 atomically.



I tried to read the cache code (btw an excellent exercise) , and I think I
understand how persistent data structure/atom is employed here in case we
deal with side effects free functions.


 You could write this as a function. There's nothing in there that requires
 a macro.

 (defn when-map-future-swap! [a k f]
   (locking a
 (when-not (contains? @a k)
   (swap! a assoc k nil)
   (future (swap! a assoc k (f k))


I realized that later too ...

But I'd personally just use a delay rather than locking for this purpose.


It is not that I like locking at all. However I still fail to see, how in a
multithreaded context memoize/cache prevents executing a given function
more than once (which I want to avoid at any cost here) since cache lookup
and swap! does not seem to be atomic :
https://github.com/clojure/core.cache/blob/master/src/main/clojure/clojure/core/cache.clj#L52
.

Best regards,
Andy

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-08 Thread Michał Marczyk
On 8 December 2014 at 17:54, Andy L core.as...@gmail.com wrote:
 But I'd personally just use a delay rather than locking for this
 purpose.


 It is not that I like locking at all. However I still fail to see, how in a
 multithreaded context memoize/cache prevents executing a given function more
 than once (which I want to avoid at any cost here) since cache lookup and
 swap! does not seem to be atomic :
 https://github.com/clojure/core.cache/blob/master/src/main/clojure/clojure/core/cache.clj#L52

When you say

  (delay (foo)),

foo will be called at most once, regardless of how many times you
deref (@) / force the delay. (If you never force the delay, it will
not be called at all.) The way this is enforced is through making
deref a synchronized method on delays.

Cheers,
Michał

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-08 Thread Fluid Dynamics
On Monday, December 8, 2014 3:34:05 PM UTC-5, Michał Marczyk wrote:

 On 8 December 2014 at 17:54, Andy L core@gmail.com javascript: 
 wrote: 
  But I'd personally just use a delay rather than locking for this 
  purpose. 
  
  
  It is not that I like locking at all. However I still fail to see, how 
 in a 
  multithreaded context memoize/cache prevents executing a given function 
 more 
  than once (which I want to avoid at any cost here) since cache lookup 
 and 
  swap! does not seem to be atomic : 
  
 https://github.com/clojure/core.cache/blob/master/src/main/clojure/clojure/core/cache.clj#L52
  

 When you say 

   (delay (foo)), 

 foo will be called at most once, regardless of how many times you 
 deref (@) / force the delay. (If you never force the delay, it will 
 not be called at all.) The way this is enforced is through making 
 deref a synchronized method on delays. 


Which means it's locking or bust. You just get to either do the locking 
yourself or delegate :) 

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-08 Thread Michał Marczyk
Oh, and as for how to use it here, you could for example say

  (.putIfAbsent concurrent-hash-map :foo (delay (foo)))

Then the first thread to @(get concurrent-hash-map :foo (delay
:not-found)) (or similar) would actually compute the value.

With a map in an atom, you could swap! using a function like

  (fn [old-state]
(if (contains? old-state :foo)
  (assoc old-state :foo (delay (foo)))
  old-state))

I'd probably prefer a CHM for this purpose, though.

Michał


On 8 December 2014 at 21:33, Michał Marczyk michal.marc...@gmail.com wrote:
 On 8 December 2014 at 17:54, Andy L core.as...@gmail.com wrote:
 But I'd personally just use a delay rather than locking for this
 purpose.


 It is not that I like locking at all. However I still fail to see, how in a
 multithreaded context memoize/cache prevents executing a given function more
 than once (which I want to avoid at any cost here) since cache lookup and
 swap! does not seem to be atomic :
 https://github.com/clojure/core.cache/blob/master/src/main/clojure/clojure/core/cache.clj#L52

 When you say

   (delay (foo)),

 foo will be called at most once, regardless of how many times you
 deref (@) / force the delay. (If you never force the delay, it will
 not be called at all.) The way this is enforced is through making
 deref a synchronized method on delays.

 Cheers,
 Michał

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-08 Thread Michał Marczyk
On 8 December 2014 at 21:46, Fluid Dynamics a2093...@trbvm.com wrote:
 [...]
 Which means it's locking or bust. You just get to either do the locking
 yourself or delegate :)

Sure, but isn't it nice when somebody else does your locking for you? :-)

Incidentally, there is a trade-off here between lockless reads and
cache-locking writes in the version with (locking …) and synchronized
reads (of delays) and somewhat concurrency-friendly writes in the
version with CHM.putIfAbsent and delays. So actually explicit (locking
…) might be preferable for certain workloads. Benchmarking required.

Michał


 --
 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.

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-07 Thread James Reeves
On 7 December 2014 at 05:13, Andy L core.as...@gmail.com wrote:




 The SoftCache uses a ConcurrentHashMap, but that caching option isn't
 used in core.memoize. Are you building a custom memoizer?


 WRT ConcurrentHashMap, it was an incorrect conclusion on my part. In any
 case, I fail to see thread safety in the cache implementation, but again
 I could be wrong. Or it might not be needed for 99.99% cache use cases.
 Also, at that point I would like to avoid to defcache my own version.


Most of the cache implementations in core.cache have no side-effects. They
simply return a new cache rather than overwriting the old one. The memoize
library places the cache in an atom, so it's guaranteed to change
atomically.


 So I ended up with this (this is my first LISP macro ever, so please be
 gentle :-) :

 (defmacro when-map-future-swap! [a k f]
   `(locking ~a
  (when (not (contains? @~a ~k))
(swap! ~a assoc ~k nil)
(future (swap! ~a assoc  ~k (~f ~k)))
)
  )
   )


You could write this as a function. There's nothing in there that requires
a macro.

(defn when-map-future-swap! [a k f]
  (locking a
(when-not (contains? @a k)
  (swap! a assoc k nil)
  (future (swap! a assoc k (f k))

But I'd personally just use a delay rather than locking for this purpose.

- James

-- 
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.


atoms, memoize, future-s and CAS

2014-12-06 Thread Andy L
Hi,

Here is the situation. There is a function f retrieving some data from
various sources (including reading files, a lot of io, e.g. map-reduce)
expected by design to return the same result for given input. Results of
f invocation from parallely running futures are stored in an atom wrapped
map and everything works just fine.

With a small exception when f invocations with the same arguments
overlap  - the same expensive io is kicked off 2 or more time s. Not a
tragedy, but still very unpleasant and wasteful. The expectation would be
that a subsequent f call would just bail without doing anything while
initial invocation assoc new data into an atom,

The first intuition was to use memoize, however I do not think its
semantics fit well into this case, since the result is a side-effect on
an atom as opposed to return value.

The easiest solution, is to create another atom with a map of the arguments
into a state. If some other future is already there working hard on the
problem, we simply bail. However, that leads to another problem.

compare-and-set! just operates on atoms as wholes, which is fine for
unstructured data, but  falls short for things somewhat more complex,
like here.

While updating an atom in this context is trivial:

user= (def a (atom {}))
user= (swap! a assoc [arg1 arg2] :PENDING)
{[arg1 arg2] resultA}

I would like to link the condition and future together somehow like that:

user= (when ((complement contains?) @a [arg1 arg3]) (swap! a assoc
[arg1 arg3] :PENDING ))

That obviously will not work, unless I wrap it with locking, which is not
necessarily a nice thing to do. I also tried to use refs here, however they
do not fit well here either and the solution is not as nice as it could be.

After that long introduction, I would like ask for some insight  Thanks
in advance ...


Best,
Andy

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-06 Thread James Reeves
It sounds like you want a delay. Delays are guaranteed to execute their
body only once, so we can combine a delay with an atom:

(defn memoized [f]
  (comp deref (memoize (fn [ args] (delay (apply f args)

In theory that should produce a memoize that executes the function only
once for each set of arguments.

- James

On 6 December 2014 at 21:32, Andy L core.as...@gmail.com wrote:

 Hi,

 Here is the situation. There is a function f retrieving some data from
 various sources (including reading files, a lot of io, e.g. map-reduce)
 expected by design to return the same result for given input. Results of
 f invocation from parallely running futures are stored in an atom wrapped
 map and everything works just fine.

 With a small exception when f invocations with the same arguments
 overlap  - the same expensive io is kicked off 2 or more time s. Not a
 tragedy, but still very unpleasant and wasteful. The expectation would be
 that a subsequent f call would just bail without doing anything while
 initial invocation assoc new data into an atom,

 The first intuition was to use memoize, however I do not think its
 semantics fit well into this case, since the result is a side-effect on
 an atom as opposed to return value.

 The easiest solution, is to create another atom with a map of the
 arguments into a state. If some other future is already there working
 hard on the problem, we simply bail. However, that leads to another problem.

 compare-and-set! just operates on atoms as wholes, which is fine for
 unstructured data, but  falls short for things somewhat more complex,
 like here.

 While updating an atom in this context is trivial:

 user= (def a (atom {}))
 user= (swap! a assoc [arg1 arg2] :PENDING)
 {[arg1 arg2] resultA}

 I would like to link the condition and future together somehow like that:

 user= (when ((complement contains?) @a [arg1 arg3]) (swap! a assoc
 [arg1 arg3] :PENDING ))

 That obviously will not work, unless I wrap it with locking, which is
 not necessarily a nice thing to do. I also tried to use refs here, however
 they do not fit well here either and the solution is not as nice as it
 could be.

 After that long introduction, I would like ask for some insight 
 Thanks in advance ...


 Best,
 Andy











  --
 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.


-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-06 Thread Andy L
 (defn memoized [f]
   (comp deref (memoize (fn [ args] (delay (apply f args)


Thanks for looking into that. This indeed would solve a semantics problem
of memoize, as it returns a value now. However, it seems that
clojure.core.memoize,
or rather  clojure.core.cache memoize is based of, is not thread safe.

It uses ConcurrentHashMap's put under the hood, instead of atomic
putIfAbsent. I might be completely wrong here though.

Cheers,
Andy

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-06 Thread James Reeves
On 7 December 2014 at 01:13, Andy L core.as...@gmail.com wrote:

 Thanks for looking into that. This indeed would solve a semantics
 problem of memoize, as it returns a value now. However, it seems that 
 clojure.core.memoize,
 or rather  clojure.core.cache memoize is based of, is not thread safe.

 It uses ConcurrentHashMap's put under the hood, instead of atomic
 putIfAbsent. I might be completely wrong here though.


The SoftCache uses a ConcurrentHashMap, but that caching option isn't used
in core.memoize. Are you building a custom memoizer?

- James

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-06 Thread Andy L
 The SoftCache uses a ConcurrentHashMap, but that caching option isn't used
 in core.memoize. Are you building a custom memoizer?


WRT ConcurrentHashMap, it was an incorrect conclusion on my part. In any
case, I fail to see thread safety in the cache implementation, but again
I could be wrong. Or it might not be needed for 99.99% cache use cases.
Also, at that point I would like to avoid to defcache my own version.


So I ended up with this (this is my first LISP macro ever, so please be
gentle :-) :

(defmacro when-map-future-swap! [a k f]
  `(locking ~a
 (when (not (contains? @~a ~k))
   (swap! ~a assoc ~k nil)
   (future (swap! ~a assoc  ~k (~f ~k)))
   )
 )
  )

Which seems to do what I need:

user= (defn f[k] (Thread/sleep 3000) k)
#'user/f
user= (when-map-future-swap! a arg f)
#core$future_call$reify__6320@a7f0cb9: :pending
user= a
#Atom@71d68915: {arg nil}
user= (Thread/sleep 4000)
nil
user= a
#Atom@71d68915: {arg arg}


Andy

-- 
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.


Re: atoms, memoize, future-s and CAS

2014-12-06 Thread Andy L
or even better (using future themselves as a marker in the atom):

(defmacro map-future-swap! [a k f]
  `(locking ~a
 (when (not (contains? @~a ~k))
   (swap! ~a assoc ~k (future (swap! ~a assoc  ~k (~f ~k
   )
 )
  )

-- 
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.