Raku parallel/functional 方法
Raku parallel/functional methods
我对 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
代码。
由于 reduce
的性质,我认为 race
不会自动并行化 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
and/or reduce
计算涉及 Num
s(浮点数)。浮点数的加法是不可交换的,所以如果加法最终以不同的顺序发生,你可能会得到(通常很小的)差异。
我想这解释了 0.004
的区别。
你的顺序 reduce
比你的 for
慢 2 倍
my execution time doubled (I am applying this to roughly 1000 elements)
首先,如上所述,您的 reduce
代码不同。存在一般的抽象差异(例如,每次调用采用两个参数而不是 for
块的一个),并且您的特定数据可能导致基本的数字计算差异(也许您的 for
循环计算主要是整数或浮点数学而你的 reduce
主要是理性的?)。 可能 解释了执行时间的差异,或者其中的一部分。
一方面,它的另一部分可能是 reduce
之间的区别,后者默认编译为闭包调用,具有调用开销,每个调用有两个参数,并且临时内存存储中间结果,另一方面,for
默认编译为直接迭代,{...}
只是内联代码而不是闭包调用。 (也就是说,reduce
有时可能会编译为内联代码;您的代码甚至可能已经是这样了。)
更一般地说,Rakudo 优化工作仍处于相对早期的阶段。其中大部分是通用的,可以加速所有代码。在对特定结构进行了努力的地方,到目前为止,最广泛使用的结构得到了关注,for
被广泛使用,而 reduce
则较少。所以部分或全部差异可能只是 reduce
优化不佳。
在 reduce
上 race
my execution time [for .race.reduce(...)
] is 4 times higher than with the original sequential code
我认为 reduce
不会 自动 与 race
并行化。根据 its doc,reduce
由 "iteratively applying a function which knows how to combine two values" 工作,每次迭代中的一个参数是前一次迭代的结果。所以在我看来它必须按顺序完成。
(我在评论中看到我误解了编译器可以通过归约完成什么。也许这是交换操作?)
总而言之,您的代码产生了 race
ing 的开销而没有获得任何好处。
一般race
假设您正在使用 可与 race
.
并行的某些操作
首先,如您所述,race
会产生开销。将有初始化和拆卸成本,至少其中一些成本是针对整体 statement/expression 的每次评估重复支付的,即 race
d.
其次,至少目前,race
意味着在 CPU 核心上使用线程 运行ning。对于某些有效载荷,尽管有任何初始化和拆卸成本,但仍能产生有用的好处。但它充其量只是与内核数量相等的加速。
(编译器实现者总有一天会发现 race
d for
循环足够简单,可以在 GPU 上实现 运行 而不是 CPU,然后将其发送到 GPU 以实现惊人的加速。)
第三,如果你真的写 .race.foo...
你会得到 默认 赛车的一些可调方面的设置。默认值几乎肯定不是最佳的,可能会偏离。
当前可调设置为 :batch
和 :degree
。有关详细信息,请参阅 their doc。
更一般地说,并行化是否加速代码取决于特定用例的细节,例如使用的数据和硬件。
关于将 race
与 for
一起使用
如果您稍微重写一下代码,您可以 race
您的 for
:
$foo = sum do race for @_ { ($_ - $mittel)**2 }
要应用调整,您必须重复 race
作为一种方法,例如:
$foo = sum do race for @_.race(:degree(8)) { ($_ - $mittel)**2 }
我对 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
代码。由于
reduce
的性质,我认为race
不会自动并行化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
and/or reduce
计算涉及 Num
s(浮点数)。浮点数的加法是不可交换的,所以如果加法最终以不同的顺序发生,你可能会得到(通常很小的)差异。
我想这解释了 0.004
的区别。
你的顺序 reduce
比你的 for
慢 2 倍
my execution time doubled (I am applying this to roughly 1000 elements)
首先,如上所述,您的 reduce
代码不同。存在一般的抽象差异(例如,每次调用采用两个参数而不是 for
块的一个),并且您的特定数据可能导致基本的数字计算差异(也许您的 for
循环计算主要是整数或浮点数学而你的 reduce
主要是理性的?)。 可能 解释了执行时间的差异,或者其中的一部分。
一方面,它的另一部分可能是 reduce
之间的区别,后者默认编译为闭包调用,具有调用开销,每个调用有两个参数,并且临时内存存储中间结果,另一方面,for
默认编译为直接迭代,{...}
只是内联代码而不是闭包调用。 (也就是说,reduce
有时可能会编译为内联代码;您的代码甚至可能已经是这样了。)
更一般地说,Rakudo 优化工作仍处于相对早期的阶段。其中大部分是通用的,可以加速所有代码。在对特定结构进行了努力的地方,到目前为止,最广泛使用的结构得到了关注,for
被广泛使用,而 reduce
则较少。所以部分或全部差异可能只是 reduce
优化不佳。
在 reduce
上 race
my execution time [for
.race.reduce(...)
] is 4 times higher than with the original sequential code
我认为 reduce
不会 自动 与 race
并行化。根据 its doc,reduce
由 "iteratively applying a function which knows how to combine two values" 工作,每次迭代中的一个参数是前一次迭代的结果。所以在我看来它必须按顺序完成。
(我在评论中看到我误解了编译器可以通过归约完成什么。也许这是交换操作?)
总而言之,您的代码产生了 race
ing 的开销而没有获得任何好处。
一般race
假设您正在使用 可与 race
.
首先,如您所述,race
会产生开销。将有初始化和拆卸成本,至少其中一些成本是针对整体 statement/expression 的每次评估重复支付的,即 race
d.
其次,至少目前,race
意味着在 CPU 核心上使用线程 运行ning。对于某些有效载荷,尽管有任何初始化和拆卸成本,但仍能产生有用的好处。但它充其量只是与内核数量相等的加速。
(编译器实现者总有一天会发现 race
d for
循环足够简单,可以在 GPU 上实现 运行 而不是 CPU,然后将其发送到 GPU 以实现惊人的加速。)
第三,如果你真的写 .race.foo...
你会得到 默认 赛车的一些可调方面的设置。默认值几乎肯定不是最佳的,可能会偏离。
当前可调设置为 :batch
和 :degree
。有关详细信息,请参阅 their doc。
更一般地说,并行化是否加速代码取决于特定用例的细节,例如使用的数据和硬件。
关于将 race
与 for
一起使用
如果您稍微重写一下代码,您可以 race
您的 for
:
$foo = sum do race for @_ { ($_ - $mittel)**2 }
要应用调整,您必须重复 race
作为一种方法,例如:
$foo = sum do race for @_.race(:degree(8)) { ($_ - $mittel)**2 }