2010/8/4 David Nolen <dnolen.li...@gmail.com>

> Have you considered that you're realizing very large lazy sequences and
> might be thrashing around in GC ? The parallel versions needs X times the
> available memory of the sequential version, where X is the number of
> concurrent threads right?


David, I don't think so, the burn function does not seem to hold onto the
head.

There's indeed a "potential" problem in the pmap version since it holds onto
the head of n-sized sequence, but since n ranges from 4 to 32 it hardly can
be the problem.

Lee, I don't have the general answer, but as a related note, I think there
may be a problem with the "with futures" version:

a. you quickly "bootstrap" all futures in the inner call to map
b. you collect the results of the futures in parallel in the outer call to
pmap

as a reminder:
(defn burn-via-futures [n]
 (print n " burns via futures: ")
 (time (doall (pmap deref (map (fn [_] (future (burn)))
                                                 (range n))))))

problem #1: since map is lazy, the bootstrapping of the futures will follow
the consumption of the seq by pmap (modulo chunked seq behavior). So to
quickly bootstrap all your futures before passing the seq to pmap, you
should wrap the (map) inside a doall
problem #2: maybe deref is a quick enough operation that using pmap with
deref does not make sense (or would make sense if the number of cores were
realllly big, e.g. if the coll would be of size 1.000.000 and the number of
core of the same magnitude order.



> David
>
>
> On Wed, Aug 4, 2010 at 10:36 AM, Lee Spector <lspec...@hampshire.edu>wrote:
>
>>
>> Apologies for the length of this message -- I'm hoping to be complete, but
>> that made the message pretty long.
>>
>> Also BTW most of the tests below were run using Clojure 1.1. If part of
>> the answer to my questions is "use 1.2" then I'll upgrade ASAP (but I
>> haven't done so yet because I'd prefer to be confused by one thing at a time
>> :-). I don't think that can be the full answer, though, since the last batch
>> of runs below WERE run under 1.2 and they're also problematic...
>>
>> Also, for most of the runs described here (with the one exception noted
>> below) I am running under Linux:
>>
>> [lspec...@fly ~]$ cat /proc/version
>> Linux version 2.6.18-164.6.1.el5 (mockbu...@builder10.centos.org) (gcc
>> version 4.1.2 20080704 (Red Hat 4.1.2-46)) #1 SMP Tue Nov 3 16:12:36 EST
>> 2009
>>
>> with this Java version:
>>
>> [lspec...@fly ~]$ java -version
>> java version "1.6.0_16"
>> Java(TM) SE Runtime Environment (build 1.6.0_16-b01)
>> Java HotSpot(TM) 64-Bit Server VM (build 14.2-b01, mixed mode)
>>
>> SO: Most of the documentation and discussion about clojure concurrency is
>> about managing state that may be shared between concurrent processes, but I
>> have what I guess are more basic questions about how concurrent processes
>> can/should be started even in the absence of shared state (or when all
>> that's shared is immutable) and about how to get the most out of concurrency
>> on multiple cores.
>>
>> I often have large numbers of relatively long, independent processes and I
>> want to farm them out to multiple cores. (For those who care this is often
>> in the context of evolutionary computation systems, with each of the
>> processes being a fitness test.) I had thought that I was farming these out
>> in the right way to multiple cores, using agents or sometimes just pmap, but
>> then I noticed that my runtimes weren't scaling in the way that I expected
>> across machines with different numbers of cores (even though I usually saw
>> near total utilization of all cores in "top").
>>
>> This led me to do some more systematic testing and I'm confused/concerned
>> about what I'm seeing, so I'm going to present my tests and results here in
>> the hope that someone can clear things up for me. I know that timing things
>> in clojure can be complicated both on account of laziness and on account of
>> optimizations that happen on the Java side, but I think I've done the right
>> things to avoid getting tripped up too much by these issues. Still, it's
>> quite possible that I've coded some things incorrectly and/or that I'm
>> misunderstanding some basic concepts, and I'd appreciate any help that
>> anyone can provide.
>>
>> First I defined a function that would take a non-trivial amount of time to
>> execute, as follows:
>>
>> (defn burn
>>  ([] (count
>>        (take 1E6
>>          (repeatedly
>>            #(* 9999999999 9999999999)))))
>>  ([_] (burn)))
>>
>> The implementation with an ignored argument just serves to make some of my
>> later calls neater -- I suppose I might incur a tiny additional cost when
>> calling it that way but this will be swamped by the things I'm timing.
>>
>> Then I defined functions for calling this multiple times either
>> sequentially or concurrently, using three different techniques for starting
>> the concurrent processes:
>>
>> (defn burn-sequentially [n]
>>  (print n " sequential burns: ")
>>  (time (dotimes [i n] (burn))))
>>
>> (defn burn-via-pmap [n]
>>  (print n " burns via pmap: ")
>>  (time (doall (pmap burn (range n)))))
>>
>> (defn burn-via-futures [n]
>>  (print n " burns via futures: ")
>>  (time (doall (pmap deref (map (fn [_] (future (burn)))
>>                                                  (range n))))))
>>
>> (defn burn-via-agents [n]
>>  (print n " burns via agents: ")
>>  (time (let [agents (map #(agent %) (range n))]
>>          (dorun (map #(send % burn) agents))
>>          (apply await agents))))
>>
>> Finally, since there's often quite a bit of variability in the run time of
>> these things (maybe because of garbage collection? Optimization? I'm not
>> sure), I define a simple macro to execute a call three times:
>>
>> (defmacro thrice [expression]
>>  `(do ~expression ~expression ~expression))
>>
>> Now I can do some timings, and I'll first show you what happens in one of
>> the cases where everything performs as expected.
>>
>> On a 16-core machine (details at
>> http://fly.hampshire.edu/ganglia/?p=2&c=Rocks-Cluster&h=compute-4-1.local),
>> running four burns thrice, with the code:
>>
>> (thrice (burn-sequentially 4))
>> (thrice (burn-via-pmap 4))
>> (thrice (burn-via-futures 4))
>> (thrice (burn-via-agents 4))
>>
>> I get:
>>
>> 4  sequential burns: "Elapsed time: 2308.616 msecs"
>> 4  sequential burns: "Elapsed time: 1510.207 msecs"
>> 4  sequential burns: "Elapsed time: 1182.743 msecs"
>> 4  burns via pmap: "Elapsed time: 470.988 msecs"
>> 4  burns via pmap: "Elapsed time: 457.015 msecs"
>> 4  burns via pmap: "Elapsed time: 446.84 msecs"
>> 4  burns via futures: "Elapsed time: 417.368 msecs"
>> 4  burns via futures: "Elapsed time: 401.444 msecs"
>> 4  burns via futures: "Elapsed time: 398.786 msecs"
>> 4  burns via agents: "Elapsed time: 421.103 msecs"
>> 4  burns via agents: "Elapsed time: 426.775 msecs"
>> 4  burns via agents: "Elapsed time: 408.416 msecs"
>>
>> The improvement from the first line to the second is something I always
>> see (along with frequent improvements across the three calls in a "thrice"),
>> and I assume this is due to optimizations talking place in the JVM. Then we
>> see that all of the ways of starting concurrent burns perform about the
>> same, and all produce a speedup over the sequential burns of somewhere in
>> the neighborhood of 3x-4x. Pretty much exactly what I would expect and want.
>> So far so good.
>>
>> However, in the same JVM launch I then went on to do the same thing but
>> with 16 and then 48 burns in each call:
>>
>> (thrice (burn-sequentially 16))
>> (thrice (burn-via-pmap 16))
>> (thrice (burn-via-futures 16))
>> (thrice (burn-via-agents 16))
>>
>> (thrice (burn-sequentially 48))
>> (thrice (burn-via-pmap 48))
>> (thrice (burn-via-futures 48))
>> (thrice (burn-via-agents 48))
>>
>> This produced:
>>
>> 16  sequential burns: "Elapsed time: 5821.574 msecs"
>> 16  sequential burns: "Elapsed time: 6580.684 msecs"
>> 16  sequential burns: "Elapsed time: 6648.013 msecs"
>> 16  burns via pmap: "Elapsed time: 5953.194 msecs"
>> 16  burns via pmap: "Elapsed time: 7517.196 msecs"
>> 16  burns via pmap: "Elapsed time: 7380.047 msecs"
>> 16  burns via futures: "Elapsed time: 1168.827 msecs"
>> 16  burns via futures: "Elapsed time: 1068.98 msecs"
>> 16  burns via futures: "Elapsed time: 1048.745 msecs"
>> 16  burns via agents: "Elapsed time: 1041.05 msecs"
>> 16  burns via agents: "Elapsed time: 1030.712 msecs"
>> 16  burns via agents: "Elapsed time: 1041.139 msecs"
>> 48  sequential burns: "Elapsed time: 15909.333 msecs"
>> 48  sequential burns: "Elapsed time: 14825.631 msecs"
>> 48  sequential burns: "Elapsed time: 15232.646 msecs"
>> 48  burns via pmap: "Elapsed time: 13586.897 msecs"
>> 48  burns via pmap: "Elapsed time: 3106.56 msecs"
>> 48  burns via pmap: "Elapsed time: 3041.272 msecs"
>> 48  burns via futures: "Elapsed time: 2968.991 msecs"
>> 48  burns via futures: "Elapsed time: 2895.506 msecs"
>> 48  burns via futures: "Elapsed time: 2818.724 msecs"
>> 48  burns via agents: "Elapsed time: 2802.906 msecs"
>> 48  burns via agents: "Elapsed time: 2754.364 msecs"
>> 48  burns via agents: "Elapsed time: 2743.038 msecs"
>>
>> Looking first at the 16-burn runs, we see that concurrency via pmap is
>> actually generally WORSE than sequential. I cannot understand why this
>> should be the case. I guess if I were running on a single core I would
>> expect to see a slight loss when going to pmap because there would be some
>> cost for managing the 16 threads that wouldn't be compensated for by actual
>> concurrency. But I'm running on 16 cores and I should be getting a major
>> speedup, not a slowdown. There are only 16 threads, so there shouldn't be a
>> lot of time lost to overhead.
>>
>> Also interesting, in this case when I start the processes using futures or
>> agents I DO see a speedup. It's on the order of 6x-7x, not close to the 16x
>> that I would hope for, but at least it's a speedup. Why is this so different
>> from the case with pmap? (Recall that my pmap-based method DID produce about
>> the same speedup as my other methods when doing only 4 burns.)
>>
>> For the calls with 48 burns we again see nearly the expected, reasonably
>> good pattern with all concurrent calls performing nearly equivalently (I
>> suppose that the steady improvement over all of the calls is again some kind
>> of JVM optimization), with a speedup in the concurrent calls over the
>> sequential calls in the neighborhood of 5x-6x. Again, not the ~16x that I
>> might hope for, but at least it's in the right direction. The very first of
>> the pmap calls with 48 burns is an anomaly, with only a slight improvement
>> over the sequential calls, so I suppose that's another small mystery.
>>
>> The big mystery so far, however, is in the case of the 16 burns via pmap,
>> which is bizarrely slow on this 16-core machine.
>>
>> Next I tried the same thing on a 48 core machine (
>> http://fly.hampshire.edu/ganglia/?p=2&c=Rocks-Cluster&h=compute-4-2.local).
>> Here I got:
>>
>> 4  sequential burns: "Elapsed time: 3062.871 msecs"
>> 4  sequential burns: "Elapsed time: 2249.048 msecs"
>> 4  sequential burns: "Elapsed time: 2417.677 msecs"
>> 4  burns via pmap: "Elapsed time: 705.968 msecs"
>> 4  burns via pmap: "Elapsed time: 679.865 msecs"
>> 4  burns via pmap: "Elapsed time: 685.017 msecs"
>> 4  burns via futures: "Elapsed time: 687.097 msecs"
>> 4  burns via futures: "Elapsed time: 636.543 msecs"
>> 4  burns via futures: "Elapsed time: 660.116 msecs"
>> 4  burns via agents: "Elapsed time: 708.163 msecs"
>> 4  burns via agents: "Elapsed time: 709.433 msecs"
>> 4  burns via agents: "Elapsed time: 713.536 msecs"
>> 16  sequential burns: "Elapsed time: 8065.446 msecs"
>> 16  sequential burns: "Elapsed time: 8069.239 msecs"
>> 16  sequential burns: "Elapsed time: 8102.791 msecs"
>> 16  burns via pmap: "Elapsed time: 11288.757 msecs"
>> 16  burns via pmap: "Elapsed time: 12182.506 msecs"
>> 16  burns via pmap: "Elapsed time: 14609.397 msecs"
>> 16  burns via futures: "Elapsed time: 2519.603 msecs"
>> 16  burns via futures: "Elapsed time: 2436.699 msecs"
>> 16  burns via futures: "Elapsed time: 2776.869 msecs"
>> 16  burns via agents: "Elapsed time: 2178.028 msecs"
>> 16  burns via agents: "Elapsed time: 2871.38 msecs"
>> 16  burns via agents: "Elapsed time: 2244.687 msecs"
>> 48  sequential burns: "Elapsed time: 24118.218 msecs"
>> 48  sequential burns: "Elapsed time: 24096.667 msecs"
>> 48  sequential burns: "Elapsed time: 24057.327 msecs"
>> 48  burns via pmap: "Elapsed time: 10369.224 msecs"
>> 48  burns via pmap: "Elapsed time: 6837.071 msecs"
>> 48  burns via pmap: "Elapsed time: 4163.926 msecs"
>> 48  burns via futures: "Elapsed time: 3980.298 msecs"
>> 48  burns via futures: "Elapsed time: 4066.35 msecs"
>> 48  burns via futures: "Elapsed time: 4068.199 msecs"
>> 48  burns via agents: "Elapsed time: 4012.069 msecs"
>> 48  burns via agents: "Elapsed time: 4052.759 msecs"
>> 48  burns via agents: "Elapsed time: 4085.018 msecs"
>>
>> Essentially this is the same picture that I got on the 16-core machine:
>> decent (but less than I would like -- only something like 3x-4x) speedups
>> with most concurrent methods in most cases but a bizarre anomaly with 16
>> burns started with pmap, which is again considerably slower than the
>> sequential runs. Why should this be? When I run only 4 burns or a full 48
>> burns the pmap method performs decently (that is, at least things get faster
>> than the sequential calls), but with 16 burns something very odd happens.
>>
>> Finally, I ran the same thing on my MacBook Pro 3.06 GHz Intel Core 2 Duo,
>> Mac OS X 10.6.4, with Clojure 1.2.0-master-SNAPSHOT under
>> Eclipse/Counterclockwise, with a bunch of applications running, so probably
>> this is acting more or less like a single core machine, and got:
>>
>> 4  sequential burns: "Elapsed time: 3487.224 msecs"
>> 4  sequential burns: "Elapsed time: 2327.569 msecs"
>> 4  sequential burns: "Elapsed time: 2137.697 msecs"
>> 4  burns via pmap: "Elapsed time: 12478.725 msecs"
>> 4  burns via pmap: "Elapsed time: 12815.75 msecs"
>> 4  burns via pmap: "Elapsed time: 8464.909 msecs"
>> 4  burns via futures: "Elapsed time: 11494.17 msecs"
>> 4  burns via futures: "Elapsed time: 12365.537 msecs"
>> 4  burns via futures: "Elapsed time: 12098.571 msecs"
>> 4  burns via agents: "Elapsed time: 10361.749 msecs"
>> 4  burns via agents: "Elapsed time: 12458.174 msecs"
>> 4  burns via agents: "Elapsed time: 9016.093 msecs"
>> 16  sequential burns: "Elapsed time: 8706.674 msecs"
>> 16  sequential burns: "Elapsed time: 8748.006 msecs"
>> 16  sequential burns: "Elapsed time: 8729.54 msecs"
>> 16  burns via pmap: "Elapsed time: 46022.281 msecs"
>> 16  burns via pmap: "Elapsed time: 44845.725 msecs"
>> 16  burns via pmap: "Elapsed time: 45393.156 msecs"
>> 16  burns via futures: "Elapsed time: 52822.863 msecs"
>> 16  burns via futures: "Elapsed time: 50647.708 msecs"
>> 16  burns via futures: "Elapsed time: 50337.916 msecs"
>> 16  burns via agents: "Elapsed time: 48615.905 msecs"
>> 16  burns via agents: "Elapsed time: 56703.723 msecs"
>> 16  burns via agents: "Elapsed time: 69765.913 msecs"
>> 48  sequential burns: "Elapsed time: 38885.616 msecs"
>> 48  sequential burns: "Elapsed time: 38651.573 msecs"
>> 48  sequential burns: "Elapsed time: 36669.02 msecs"
>> 48  burns via pmap: "Elapsed time: 169108.022 msecs"
>> 48  burns via pmap: "Elapsed time: 176656.455 msecs"
>> 48  burns via pmap: "Elapsed time: 182119.986 msecs"
>> 48  burns via futures: "Elapsed time: 176764.722 msecs"
>> 48  burns via futures: "Elapsed time: 169257.577 msecs"
>> 48  burns via futures: "Elapsed time: 157205.693 msecs"
>> 48  burns via agents: "Elapsed time: 140618.333 msecs"
>> 48  burns via agents: "Elapsed time: 137992.773 msecs"
>> 48  burns via agents: "Elapsed time: 143153.696 msecs"
>>
>> Here we have a very depressing picture. Although I wouldn't expect to get
>> any speedup from concurrency the concurrency-related slowdowns have now
>> spread to all of my concurrency-starting methods with all numbers of burns.
>> It is way way way worse to be using the concurrency methods than the
>> straightforward sequential method in every circumstance. Again, I understand
>> why one should expect a small loss in a case like this, but these are huge
>> losses and the number of threads that have to be coordinated (with no
>> shared) is quite small -- just 4-48.
>>
>> My guess is that all of this is stemming from some confusion on my part
>> about how I should be starting and managing concurrent processes, and my
>> greatest hope is that one of you will show me an alternative to my
>> burn-via-* functions that provides a speedup nearly linear with the number
>> of cores and only a negligible loss when there's only one core available...
>>
>> But any help of any kind would be appreciated.
>>
>> Thanks,
>>
>>  -Lee
>>
>> --
>> Lee Spector, Professor of Computer Science
>> School of Cognitive Science, Hampshire College
>> 893 West Street, Amherst, MA 01002-3359
>> lspec...@hampshire.edu, http://hampshire.edu/lspector/
>> Phone: 413-559-5352, Fax: 413-559-5438
>>
>> Check out Genetic Programming and Evolvable Machines:
>> http://www.springer.com/10710 - http://gpemjournal.blogspot.com/
>>
>> --
>> 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<clojure%2bunsubscr...@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 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<clojure%2bunsubscr...@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 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

Reply via email to