[plt-dev] Parallel Futures Release
On Mon, Dec 7, 2009 at 1:08 PM, Eli Barzilay <eli at barzilay.org> wrote:
> On Dec 7, James Swaine wrote:
> > I'm pleased to announce the initial release of parallel futures, a
> > construct for fine-grained parallelism in PLT.
>
> Random notes:
>
> 1. Your message mentioned requiring `scheme/futures' -- it should be
> `scheme/future'.
> 2. Is it possible to move `processor-count' into the core and make it
> available for non-future builds too? That function is useful even
> without futures (for example, for code that runs subprocesses for
> all cores -- in my scripts I just read a from from /proc, and
> having a portable replacement would be nice). For the sake of code
> that wants to use futures, I think that the current function is
> better named `futures-count'.
>
This sounds fine to me. processor-count should always have the same
behavior, regardless
of whether futures are enabled. But I'm not sure why you would need a
separate function for
futures-count, unless you're intending it to be related to question (6)
below?
>
> 3. Can you please include the command line for building with futures
> support? This way, you can have it show both `--enable-futures'
> and `--disable-mred' to avoid confusion.
>
Yes, will do.
>
> 4. You should also mention what Windows users should do, even if it's
> "not use futures", since on windows there is no configure step.
>
> 5. The term `unsafe' was used with a particular meaning by the foreign
> interface, then came the scheme/unsafe/ops library with a slightly
> different but related meaning. The futures documentation uses
> `unsafe' with a completely different meaning -- so it would be
> better to find some other term, or at least make that very clear.
> (This applies in general too -- for example, in your reply to Jay
> you mention "unsafe primitives"... A question about the confusion
> with `scheme/unsafe/ops' or `scheme/foreign' would also be
> difficult to answer when the only term you have is "unsafe".)
>
Good point - perhaps it'd be best to use other terminology to avoid
confusion here.
>
> 6. It would be nice to be able to get a list of the currently running
> futures? (With threads, this is possible through custodians --
> with futures being global this won't make much sense.)
>
> 7. There should be some way to disable futures. I'm thinking of
> situations like running sandboxed code, where you don't want
> untrusted code to be able to grab cpus. Of course it would be nice
> to be able to have a finer control over it (like how many cores
> each future sees) -- but with nested futures being forbidden there
> doesn't seem to be much point in that.
>
> 8. Looks like there is no way to kill a running future. There should
> be one. To make it practical, it should also be killed on a
> custodian shutdown -- but again, it's not clear to me how that
> might look when futures are inherently global to the process.
>
> 9. Worse, it looks like when a future is touched, it's not really
> joining the main execution thread. For example, when I run your
> suggested example:
>
> (for-each touch
> (for/list ([i (in-range 0 (processor-count))])
> (future loop)))
>
> I get my 4 cores spinning away happily, but hitting break leaves
> them *all* running. I'd expect the first future to stop as a
> result. And with future values giving you only the ability to
> touch, this means that your example is a way to render your
> mzscheme process useless until it exits.
>
> 10. (This will be a long one.)
>
> The "unsafe" primitives make some sense as far as using them.
> (For example, it's clear that using any IO will suspend the
> future.)
>
> But there is no mention at all of what happens when there are
> potential race conditions. Is writing to memory going to block a
> future in any way? How is reading handled? Since this is not
> documented, I ran some experiments. (My machine has two double
> core Xeons cpus at 3ghz, in case it matters.)
>
> First, I did this:
>
> #lang scheme
> (require scheme/future)
> (define x
> (let ([v (vector 0 0 0 0)])
> (define ((spin n))
> (let loop () (vector-set! v n (add1 (vector-ref v n))) (loop)))
> (cons v (map (compose future spin) '(0 1 2 3)))))
> (sleep 10)
> x
> (exit)
>
> and the printout had similar numbers, between 53M and 58M.
> Realizing that there's nothing useful I can do with the future
> values, I changed it to:
>
> (define x
> (let ([v (vector 0 0 0 0)])
> (define ((spin n))
> (let loop () (vector-set! v n (add1 (vector-ref v n))) (loop)))
> (for-each (compose future spin) '(0 1 2 3))
> v))
>
> and this produced very different numbers, with one counter being
> at around 110M, two more around 50-60M, and one around 10M. The
> high one was usually the first, and the low one was usually the
> last. I couldn't really attribute this to delays in firing off
> the futures, because one of the results was #(74M 49M 98M 10M).
> Next I tried to rephrase it, to avoid delays in starting them as
> soon as possible:
>
> (define x
> (let ([v (vector 0 0 0 0)])
> (define (spin n)
> (future
> (lambda ()
> (let loop () (vector-set! v n (add1 (vector-ref v n)))
> (loop)))))
> (for-each spin '(0 1 2 3))
> v))
>
> and the results went back to all being around 60M. The *sum* of
> all counters is roughly the same (around 230M), but I can't think
> of any way the previous example would have such huge differences.
>
> Next, I tried this:
>
> (define x
> (let ([v (vector 0 0)])
> (define (spin n)
> (future
> (lambda ()
> (let loop () (vector-set! v n (add1 (vector-ref v n)))
> (loop)))))
> (for-each spin '(0 1 1 1))
> v))
>
> and the counts were 63M and 77M resp. This *looks* like reading
> and writing from the same memory location is "safe", and you get
> the usual races -- so the three cores that use the second counter
> keep running into races of "read N, write N+1 after another thread
> wrote N+1 or more", resulting in races mostly making the counts
> the same, but with some "success rate" that leads to a higher
> number.
>
> If this is correct, and together with the fact that the usual
> synchronization primitives in mzscheme are useless in futures
> (since they seem to be "unsafe"), then this is a pretty important
> thing to describe in the docs. Without being able to sync, then
> people should be told very explicitly that the system avoids race
> conditions that violate the mzscheme vm, but it knows nothing
> about your own races, which means that you should be extra careful
> to use futures for "very" independent tasks.
>
An excellent point - I'll update the documentation to include something to
this effect. We've had several discussions about adding concurrency
primitives
with futures - but you're right, as of now you're restricted to independent
tasks because there really isn't an effective way to
do inter-thread communication.
>
> --
> ((lambda (x) (x x)) (lambda (x) (x x))) Eli Barzilay:
> http://barzilay.org/ Maze is Life!
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/dev/archive/attachments/20091207/83aa8771/attachment.html>