Raku 并行/函数方法

Spr*_*opf 8 raku

我对 Raku 很陌生,我对函数式方法有疑问,尤其是 reduce。我最初有这样的方法:

sub standardab{
  my $mittel = mittel(@_);
  my $foo = 0;
  for @_ {
    $foo += ($_ - $mittel)**2;
  }
  $foo = sqrt($foo/(@_.elems));
}
Run Code Online (Sandbox Code Playgroud)

它工作正常。然后我开始使用reduce:

sub standardab{
    my $mittel = mittel(@_);
    my $foo = 0;
    $foo = @_.reduce({$^a + ($^b-$mittel)**2});
    $foo = sqrt($foo/(@_.elems));
}
Run Code Online (Sandbox Code Playgroud)

我的执行时间增加了一倍(我将其应用于大约 1000 个元素)并且解决方案相差 0.004(我猜是舍入误差)。如果我正在使用

.race.reduce(...)
Run Code Online (Sandbox Code Playgroud)

我的执行时间比原始顺序代码高 4 倍。有人能告诉我这是什么原因吗?我考虑了并行初始化时间,但是 - 正如我所说 - 我将它应用于 1000 个元素,如果我更改代码中的其他 for 循环以减少它会变得更慢!

谢谢你的帮助

rai*_*iph 8

概括

  • 在一般情况下,reducefor做不同的事情,他们在你的代码做不同的事情。例如,与您的for代码相比,您的reduce代码涉及传递的参数数量是原来的两倍,并且迭代次数减少了一次。我认为这可能是0.004差异的根源。

  • 即使您forreduce代码做了同样的事情,此类reduce代码的优化版本也永远不会比等效for代码的同等优化版本快。

  • 我认为由于的性质,race这不会自动并行化。(尽管我根据您和 @user0721090601 的评论看到我错了。)但这产生开销 - 目前很多reducereduce

  • 如果稍微重写,您可以使用race并行化您的for循环。那可能会加快速度。

关于你的forreduce代码的区别

这是我的意思的区别:

say do for    <a b c d>  { $^a }       # (a b c d)      (4 iterations)

say do reduce <a b c d>: { $^a, $^b }  # (((a b) c) d)  (3 iterations)
Run Code Online (Sandbox Code Playgroud)

有关其操作的更多详细信息,请参阅各自的文档 ( for, reduce)。

您尚未共享您的数据,但我认为for和/或reduce计算涉及Nums(浮点数)。浮点数的添加不是可交换的,因此如果添加最终以不同的顺序发生,您很可能会得到(通常很小)差异。

我认为这可以解释0.004差异。

你的顺序reduce比你的慢 2 倍for

我的执行时间翻了一番(我将其应用于大约 1000 个元素)

首先,您的reduce代码是不同的,如上所述。存在一般的抽象差异(例如,每次调用采用两个参数而不是for块的参数),也许您的特定数据会导致基本的数值计算差异(也许您的for循环计算主要是整数或浮点数学,而您reduce主要是理性的?)。这可能解释了执行时间差异,或其中的一部分。

它的另一部分可能是,一方面, a 之间的区别,reduce默认情况下会编译成闭包的调用,具有调用开销,每个调用有两个参数,以及存储中间结果的临时内存,另一方面, afor将默认编译为直接迭代,{...}只是内联代码而不是闭包调用。(也就是说,reduce有时可能会编译为内联代码;对于您的代码,它甚至可能已经是这种方式。)

更一般地说,Rakudo 优化工作仍处于相对早期的阶段。其中大部分是通用的,加速了所有代码。在努力应用于特定结构的地方,迄今为止最广泛使用的结构已经引起了人们的注意,并且for被广泛使用和reduce较少使用。因此,部分或全部差异可能只是reduce优化不佳。

reducerace

我的执行时间 [for .race.reduce(...)] 比原始顺序代码高 4 倍

我不认为reduce自动race. 根据其 docreduce通过“迭代应用知道如何组合两个值的函数”来工作,并且每次迭代中的一个参数是前一次迭代的结果。所以在我看来它必须按顺序完成。

(我在评论中看到我误解了编译器可以通过减少做什么。也许这是一个交换操作?)

总之,您的代码会产生raceing 的开销而没有获得任何好处。

race一般

假设您正在使用一些race.

首先,正如您所指出的,race会产生开销。将有初始化和拆卸成本,至少其中一些是为正在raced的整体语句/表达式的每次评估重复支付的。

其次,至少就目前而言,这race意味着使用在 CPU 内核上运行的线程。对于某些有效载荷,尽管有任何初始化和拆卸成本,但仍能产生有用的好处。但它充其量只是与内核数量相等的加速。

(有一天,编译器实现者应该有可能发现racedfor循环足够简单,可以在 GPU 而不是 CPU 上运行,然后继续将其发送到 GPU 以实现惊人的加速。)

第三,如果您从字面上写,.race.foo...您将获得赛车某些可调方面的默认设置。默认值几乎可以肯定不是最佳的,可能还有很长的路要走。

当前可调设置为:batch:degree。有关更多详细信息,请参阅他们的文档

更一般地说,并行化是否加速代码取决于特定用例的细节,例如使用的数据和硬件。

使用racefor

如果你重写代码多是可以racefor

$foo = sum do race for @_ { ($_ - $mittel)**2 } 
Run Code Online (Sandbox Code Playgroud)

要应用调整,您必须重复race作为一种方法,例如:

$foo = sum do race for @_.race(:degree(8)) { ($_ - $mittel)**2 } 
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢,这解释了很多。我只是习惯了一些其他编程语言和一些函数式语言。例如,reduce(似乎是 scala 中的折叠)确实是高度可并行化的,但你可能是对的 - 它可能还没有在 raku 中实现 (2认同)