我对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 循环以减少它,它会变得更慢!
感谢您的帮助
总结
-
通常,
reduce
和for
执行不同的事情,它们在代码中执行不同操作。例如,与for
代码相比,reduce
代码涉及的参数传递量是其两倍,并且执行的迭代次数减少了一次。我认为这可能是0.004
差异的根源。 -
即使您的
for
和reduce
代码执行相同的操作,此类reduce
代码的优化版本也永远不会比同等优化的等效for
代码版本更快。 -
我认为
race
由于reduce
的性质,并没有自动并行化reduce
。(虽然我看到根据你和@user0721090601的评论我错了。但它会产生开销 - 目前很多。 -
您可以使用
race
来并行化for
循环,如果它稍微重写了。这可能会加快速度。
关于您的for
和reduce
代码之间的区别
这是我的意思
: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)
有关其操作的更多详细信息,请参阅其各自的文档(for
,reduce
)。
您尚未共享数据,但我假设for
和/或reduce
计算涉及Num
s(浮点数)。浮点数的添加不是可交换的,因此如果添加最终以不同的顺序发生,您可能会得到(通常很小的)差异。
我认为这解释了0.004
差异。
顺序reduce
比for
慢 2 倍
我的执行时间翻了一番(我将其应用于大约 1000 个元素)
首先,如上所述,您的reduce
代码是不同的。存在一般的抽象差异(例如,每次调用两个参数而不是for
块的一个参数),并且可能您的特定数据会导致基本的数值计算差异(也许您的for
循环计算主要是整数或浮点数学,而您的reduce
主要是理性的?这可能解释了执行时间差异或其中的一些原因。
另一部分可能是,一方面,reduce
,默认情况下,它将编译为闭包调用,具有调用开销,每次调用两个参数,以及存储中间结果的临时内存,另一方面,默认情况下将编译为直接迭代的for
,{...}
只是内联代码而不是闭包的调用。(也就是说,reduce
有时可能会编译为内联代码;甚至可能已经是这样用于您的代码。
更一般地说,Rakudo 优化工作仍处于相对早期阶段。其中大部分都是通用的,加快了所有代码的速度。在对特定结构施加努力的地方,到目前为止,使用最广泛的结构已经引起了人们的关注,for
被广泛使用,reduce
较少。因此,部分或全部差异可能只是reduce
优化不佳。
与race
reduce
我的执行时间 [对于
.race.reduce(...)
] 比原始顺序代码高 4 倍
我不认为reduce
会自动与race
并行化。根据其文档,reduce
通过"迭代应用知道如何组合两个值的函数"来工作,并且每次迭代中的一个参数是上一次迭代的结果。所以在我看来,它必须按顺序完成。
(我在评论中看到我误解了编译器可以通过减少做什么。也许这是如果它是一个交换操作?
总之,您的代码会产生race
ing 的开销,而不会获得任何好处。
一般race
假设您正在使用一些可与race
并行化的操作。
首先,正如您所指出的,race
会产生开销。将有一个初始化和拆卸成本,至少其中一些成本是为正在race
的整体语句/表达式的每次评估重复支付的。
其次,至少目前,race
意味着使用在CPU内核上运行的线程。对于某些有效负载,尽管存在任何初始化和拆卸成本,但仍能产生有用的好处。但它充其量只是与内核数量相等的加速。
(有一天,编译器实现者应该有可能发现race
dfor
循环非常简单,可以在GPU而不是CPU上运行,并继续将其发送到GPU以实现惊人的速度。
第三,如果你真的写.race.foo...
你会得到赛车某些可调方面的默认设置。默认值几乎肯定不是最佳的,并且可能相差甚远。
当前可调的设置是:batch
和:degree
。有关更多详细信息,请参阅他们的文档。
更一般地说,并行化是否加速代码取决于特定用例的详细信息,例如正在使用的数据和硬件。
将race
与for
一起使用时
如果你稍微重写一下代码,你可以race
你的for
:
$foo = sum do race for @_ { ($_ - $mittel)**2 }
要应用优化,您必须将race
作为方法重复,例如:
$foo = sum do race for @_.race(:degree(8)) { ($_ - $mittel)**2 }