Perl 6 中是否有快速并行 "for" 循环?

Is there a fast parallel "for" loop in Perl 6?

给定一些代码,它对从 1 到 500000 的每个数字执行一些 math/casting,我们有以下选项:

  1. 简单的 for 循环:for ^500000 -> $i { my $result = ($i ** 2).Str; }。在我不科学的基准测试中,这需要 2.8 秒。

  2. 最规范的并行版本在 Promise 中完成每一位工作,然后等待结果。 await do for ^500000 -> $i { start { my $result = ($i ** 2).Str; } } 需要 19 秒。这很慢!创建一个新的承诺必须有太多的开销,不值得进行如此简单的计算。

  3. 使用并行 map 操作相当快。在 2.0 秒时,操作似乎勉强慢到可以利用并行化:(^500000).race.map: -> $i { my $result = ($i ** 2).Str; }

第三个选项似乎最好。不幸的是,它读起来像黑客。我们不应该为接收器上下文中的迭代编写 map 代码,因为在源代码中阅读 "map" 的其他人可能会认为目的是构建一个列表,这根本不是我们的意图。这样使用map沟通不畅

是否有任何规范的快速方法来使用 Perl 6 的内置并发性?如果超级运算符可以接受一个块而不是仅接受函数,那么它就是完美的:

(^500000)».(-> $i { my $result = ($i ** 2).Str; }) # No such method 'CALL-ME' for invocant of type 'Int'

在我的 PC 上,这比原始循环快一点 (~15%):

(^500_000).hyper(batch => 100_000).map(-> $i { my $result = ($i ** 2).Str; })

由于循环内的计算非常快,通常并行化和同步的成本会使您从中获得的任何收益相形见绌。唯一的补救办法是大批量。

更新:使用 200_000 的批量大小,我得到了更好的结果(又快了几个百分点)。

如果你想在 hyper 或 race 操作中使用 for,你必须拼写 hyper for @blah.hyper(:batch(10_000))race for @blah.race(:batch(10_000))。或者不带参数:hyper for @blah, race for @blah.

之所以这样决定,是因为您可能有类似 for some-operation() { some-non-threadsafe-code } 的代码,其中 some-operation 是库或其他内容的一部分。现在你无法再判断 for 循环中是否可以包含线程不安全的代码,即使你知道库在那个时间点没有 return a HyperSeq,如果图书馆作者想出了这个 好主意 来通过 hypering 使 some-operation 更快?

这就是为什么 "it's safe to run this for loop in parallel" 的指示符需要在代码所在的地方,而不仅仅是在创建序列的地方。