Raku 并行/函数方法



我对Raku很陌生,我对函数方法有疑问,特别是reduce。 我原来有方法:

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

而且效果很好。然后我开始使用reduce:

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

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

.race.reduce(...)

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

感谢您的帮助

总结

  • 通常,reducefor执行不同的事情,它们在代码中执行不同操作。例如,与for代码相比,reduce代码涉及的参数传递量是其两倍,并且执行的迭代次数减少了一次。我认为这可能是0.004差异的根源。

  • 即使您的forreduce代码执行相同的操作,此类reduce代码的优化版本也永远不会比同等优化的等效for代码版本更快。

  • 我认为race由于reduce的性质,并没有自动并行化reduce。(虽然我看到根据你和@user0721090601的评论我错了。但它会产生开销 - 目前很多

  • 您可以使用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)

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

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

我认为这解释了0.004差异。

顺序reducefor慢 2 倍

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

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

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

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

racereduce

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

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

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

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

一般race

假设您正在使用一些可与race并行化的操作。

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

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

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

第三,如果你真的写.race.foo...你会得到赛车某些可调方面的默认设置。默认值几乎肯定不是最佳的,并且可能相差甚远。

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

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

racefor一起使用时

如果你稍微重写一下代码,你可以race你的for

$foo = sum do race for @_ { ($_ - $mittel)**2 } 

要应用优化,您必须将race作为方法重复,例如:

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

最新更新