我是函数式编程的新手。命令式编程中的循环取代了FP中的递归。另一个声明是FP提供高并发性。这些指令在多核/ cpu系统上并行执行,因为数据是不可变的。
在递归中,由于步骤执行取决于前面的步骤结果,因此步骤不能并行执行。
所以,我假设FP中的递归不会给出高并发性。我对么?
答案 0 :(得分:1)
排序。您无法获得比数据并行更多的执行并行性;这是Amdahl's law。但是,与典型的顺序算法(无论是功能性还是命令性)相比,您经常拥有更多的数据并行性。考虑例如采用向量的标量倍数:(注意:这是一些用于制作的algol风格的语言):1
function scalar_multiple(scalar c, vector v) {
vector v1;
for (int i = 0; i < length(v); i++) {
v1[i] = c * v[i];
}
return v1;
}
显然,这不会并行运行。如果我们使用递归(您可以将其视为Haskell)重新编写函数式语言,情况就不会得到改善:
scalar_multiple c [] = []
scalar_multiple c (x:xn) = c * x : scalar_multiple c xn
这仍然是一个顺序算法!
但是,您可以注意到没有数据依赖性 - 您实际上并不需要先前/后期乘法的结果来计算以后的数据。所以我们有可能在这里进行并行化。这可以用命令式语言来完成:
function scalar_multiple(scalar c, vector v) {
vector v1;
parallel_for (int i in 0..length(v)-1) {
v1[i] = c * v[i];
}
return v1;
}
但是这个parallel_for
是一个危险的构造。考虑搜索功能:
function first(predicate p, vector v) {
for (int i = 0; i < length(v); i++) {
if (p(v[i])) return i;
}
return -1;
}
如果我们尝试将for
替换为parallel_for
来加快速度:
function first(predicate p, vector v) {
parallel_for (int i in 0..length(v)-1) {
if (p(v[i])) return i;
}
return -1;
}
现在我们不必返回 first 元素的索引来满足条件,只需 元素即可满足条件。我们通过并行化来破坏了函数的合同。
显而易见的解决方案是{0}允许在return
内parallel_for
。但是还有很多其他危险的结构;事实上,你会注意到我不得不放弃C风格的for
循环,因为增量和测试模式本身在并行语言中是危险的。考虑:
function sequence(int n) {
vector v;
int c = 0;
parallel_for (int i = 0..n-1) {
v[i] = c++;
}
return v;
}
这又是一个玩具&#39;示例(&#34;只使用v[i] = i;
!&#34;),但它说明了一点:由于并行性,此函数以随机顺序初始化v
。事实证明,那些安全的结构是“安全的”。在像parallel_for
之类的构造中使用正是纯函数式语言中允许的构造,这使得为这些语言添加并行构造更安全&#39;而不是将它们添加到命令式语言中。
1这只是一个非常简单的例子;当然,真正的并行性涉及找到更大的工作块而不是这个!
答案 1 :(得分:0)
不确定,如果我理解你的话,但通常取决于你想要完成的事情。
单独一个递归不能并行执行其子调用。但是,您可以在同一数据集上进行2次递归。即同时通过两个并发运行的递归函数从左和右处理数组。那些(两个)函数可以(通常)并行运行。
详细说明,只要有一个可以独立运行的函数,你是否有一个递归函数或一个带有循环的函数都没关系。所以关于你的问题:
不,每个定义的递归函数不会给你任何并发性。
答案 2 :(得分:0)
循环由高阶函数替换,而不是直接递归。递归在函数式编程中是一种包罗万象的措施,因为当你的高阶函数不存在时,你需要做什么。
例如,如果要对列表的所有元素运行相同的计算,请使用高度可并行化的map
。找到符合特定条件的元素是filter
,也是高度可并行化的。
有些算法只是需要前一次迭代的结果才能继续。那些往往需要递归函数的那些,而你是对的,它们通常不容易高度并发。