问题
Given some code which does a bit of math/casting for each number from 1 to 500000, we have options:
Simple for loop:
for ^500000 -> $i { my $result = ($i ** 2).Str; }
. In my unscientific benchmark, this takes 2.8 seconds.The most canonical parallel version does each bit of work in a
Promise
, then waits for the result.await do for ^500000 -> $i { start { my $result = ($i ** 2).Str; } }
takes 19 seconds. This is slow! Creating a new promise must have too much overhead to be worthwhile for such a simple computation.Using a parallel
map
operation is fairly fast. At 2.0 seconds, the operation seems barely slow enough to take advantage of parallelization:(^500000).race.map: -> $i { my $result = ($i ** 2).Str; }
The third option seems best. Unfortunately, it reads like a hack. We should not be writing map
code for iteration in sink context, because others that read "map" in the source may assume the purpose is to build a list, which isn't our intent at all. It's poor communication to use map
this way.
Is there any canonical fast way to use Perl 6's built in concurrency? A hyper operator would be perfect if it could accept a block instead of only functions:
(^500000)».(-> $i { my $result = ($i ** 2).Str; }) # No such method 'CALL-ME' for invocant of type 'Int'
回答1:
If you want to use for with a hyper or race operation, you have to spell it hyper for @blah.hyper(:batch(10_000))
or race for @blah.race(:batch(10_000))
. Or without parameters: hyper for @blah
, race for @blah
.
This was decided because you might have code like for some-operation() { some-non-threadsafe-code }
where some-operation
is part of a library or something. Now you cannot tell any more if the for loop can have thread-unsafe code in it or not, and even if you know the library doesn't return a HyperSeq
at that point in time, what if the library author comes up with this great idea to make some-operation
faster by hypering it?
That's why a signifier for "it's safe to run this for loop in parallel" is required right where the code is, not only where the sequence gets created.
回答2:
On my PC, this is a bit (~15%) faster than the naive loop:
(^500_000).hyper(batch => 100_000).map(-> $i { my $result = ($i ** 2).Str; })
Since the computation inside the loop is really fast, typically the cost of parallelization and synchronization dwarfs any gains you get from it. The only remedy is a large batch size.
Update: with a batch size of 200_000 I get slightly better results (another few percent faster).
来源:https://stackoverflow.com/questions/47301334/is-there-a-fast-parallel-for-loop-in-perl-6