鉴于一些代码对1到500000之间的每个数字进行了一些数学/转换,我们有选择:
简单循环:for ^500000 -> $i { my $result = ($i ** 2).Str; }
。在我不科学的基准测试中,这需要2.8秒。
最规范的并行版本在Promise
中完成每项工作,然后等待结果。 await do for ^500000 -> $i { start { my $result = ($i ** 2).Str; } }
需要19秒。这很慢!创建新的承诺必须有太多的开销才能进行这样简单的计算。
使用并行map
操作相当快。在2.0秒时,操作似乎几乎不足以利用并行化:(^500000).race.map: -> $i { my $result = ($i ** 2).Str; }
第三种选择似乎最好。不幸的是,它看起来像一个黑客。我们不应该在sink上下文中编写map
代码进行迭代,因为在源代码中读取“map”的其他人可能会认为目的是构建一个列表,这根本不是我们的意图。以这种方式使用map
的沟通不畅。
有没有任何规范的快速方法来使用Perl 6的内置并发?如果一个超级运算符可以接受一个块而不仅仅是函数,那么它就是完美的:
(^500000)».(-> $i { my $result = ($i ** 2).Str; }) # No such method 'CALL-ME' for invocant of type 'Int'
答案 0 :(得分:6)
如果您想使用超级或竞赛操作,则必须拼写为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循环中是否包含线程不安全的代码,即使你知道库在那个时间点没有返回HyperSeq
,如果库作者来了怎么办?加上这个好主意,通过加强它来加快some-operation
的速度?
这就是为什么在代码所在的位置需要一个“并行运行此for循环的安全”的能力,而不仅仅是创建序列的地方。
答案 1 :(得分:3)
在我的电脑上,这比天真的循环要快一点(~15%):
(^500_000).hyper(batch => 100_000).map(-> $i { my $result = ($i ** 2).Str; })
由于循环内的计算速度非常快,因此并行化和同步的成本通常会使您获得的任何收益相形见绌。唯一的补救措施是批量大。
更新:批量大小为200_000时,我的结果会稍好一些(加快几个百分点)。