Hi Jose,
I think you should try making the core iteration purely functional,
meaning no agents, atoms, or side effecting functions like the random
generator.
I assume the number of steps you evolve the particle is encoded in
step-extract-fn?
What you probably want is something like
(loop [i 0 pos initial-position]
(if (< i num-of-steps)
(recur (+ i 1) (move pos)) ;; iterate
pos ;; if done, return final position
))
This will make it easier to benchmark the iteration step, which is an
important number to know. I'm sure you can make it much faster, if
perf is the ultimate goal its worth tuning a little.
In terms of distributing the work, I would not use atoms or agents.
They are not meant for parallelism or for work queues. With agents and
futures you need to be aware of the various thread pools involved
under the hood and make sure you are not saturating them. And combined
with laziness, it takes care to ensure work is getting done where you
are expecting it.
It would be easier to reason about what is going on by using threads
and queues directly. Enqueue a bunch of work on a queue, and directly
set up a bunch of threads that read batches of work from the queue
until its empty.
If the initial condition / other parameters are the same across
workers, you could even skip the work queue, and just set up a bunch
of threads that just do the iterations and then dump their result
somewhere.
I also definitely recommend making friends with loop/recur:
(time
(loop [i 0]
(if (< i 1000000)
(recur (+ 1 i))
true)))
=> "Elapsed time: 2.441 msecs"
(def i (atom 0))
(time
(while (< @i 1000000)
(swap! i + 1)))
=> "Elapsed time: 52.767 msecs"
loop/recur is both simpler and faster, and the best way to rapidly iterate.