【发布时间】:2008-10-08 03:59:05
【问题描述】:
我在某处读到函数式编程适合利用计算中的多核趋势。我真的不明白。和 lambda 演算和冯诺依曼架构有关吗?
【问题讨论】:
标签: f# functional-programming multicore
我在某处读到函数式编程适合利用计算中的多核趋势。我真的不明白。和 lambda 演算和冯诺依曼架构有关吗?
【问题讨论】:
标签: f# functional-programming multicore
函数式编程最大限度地减少或消除了副作用,因此更适合分布式编程。即多核处理。
换句话说,可以在不同的内核上同时独立解决许多难题,而不必担心一个操作会影响另一个操作,就像在其他编程风格中一样。
【讨论】:
处理并行处理最困难的事情之一是锁定数据结构以防止损坏。如果两个线程在没有完全锁定的情况下同时改变一个数据结构,则可能会导致从无效数据到死锁的任何事情。
相比之下,函数式编程语言倾向于强调不可变数据。任何状态都与逻辑分开,一旦创建数据结构就无法修改。大大减少了对锁定的需求。
另一个好处是一些非常容易并行化的过程,比如迭代,被抽象为函数。在 C++ 中,您可能有一个 for 循环,它对列表中的每个项目运行一些数据处理。但是编译器无法知道这些操作是否可以安全地并行运行——也许一个的结果取决于它之前的一个。当使用map()或reduce()这样的函数时,编译器可以知道调用之间没有依赖关系。因此可以同时处理多个项目。
【讨论】:
我在某处读到函数式编程适合利用计算中的多核趋势......我并没有真正明白这个想法。和 lambda 演算和冯诺依曼架构有关吗?
您引用的观点背后的论点是纯函数式编程控制副作用,这使得引入并行性变得更加容易和安全,因此,纯函数式编程语言在多核计算机的环境中应该是有利的。
不幸的是,由于以下几个原因,这种信念早已被推翻:
The absolute performance of purely functional data structures is poor。因此,纯粹的函数式编程在性能方面是朝着错误方向迈出的一大步(这是并行编程的唯一目的)。
纯函数式数据结构的扩展性很差,因为它们强调共享资源,包括分配器/GC 和主内存带宽。因此,随着内核数量的增加,并行化的纯函数式程序的加速通常会很差。
纯函数式编程会导致性能不可预测。因此,真正的纯函数式程序在并行化时经常会看到性能降级,因为粒度实际上是随机的。
例如,Haskell 社区经常引用的混蛋two-line quicksort 通常比用更传统的语言(如 F#)编写的真正的就地快速排序慢数千倍。此外,尽管您可以轻松地并行化优雅的 Haskell 程序,但您不太可能看到任何性能改进,因为所有不必要的复制会使单核使多核机器的整个主内存带宽饱和,从而使并行性毫无价值。事实上,没有人能够在 Haskell 中编写任何具有竞争性能的通用并行排序。 Haskell 标准库提供的最先进的排序通常比传统的替代方法慢数百倍。
然而,函数式编程更常见的定义是一种强调使用一流函数的风格,实际上在多核编程环境中确实非常有用,因为这种范式非常适合分解并行程序。例如,查看 .NET 4 中 System.Threading.Tasks 命名空间中新的高阶 Parallel.For 函数。
【讨论】:
当没有副作用时,评估顺序无关紧要。然后可以并行计算表达式。
【讨论】:
基本论点是很难自动并行化像 C/C++/etc 这样的语言,因为函数可以设置全局变量。考虑两个函数调用:
a = foo(b, c);
d = bar(e, f);
尽管 foo 和 bar 没有共同的参数,并且一个不依赖于另一个的返回码,但它们仍然可能具有依赖关系,因为 foo 可能会设置 bar 所依赖的全局变量(或其他副作用)。
函数式语言保证 foo 和 bar 是独立的:没有全局变量,也没有副作用。因此 foo 和 bar 可以安全地在不同的内核上自动运行,无需程序员干预。
【讨论】:
以上所有答案都指向一个关键思想,即“无共享可变存储”是并行执行程序片段的关键推动力。它并没有真正解决寻找并行执行的同样困难的问题。但是函数式语言中典型的更清晰的功能表达确实使得理论上更容易从顺序表达式中提取并行性。
在实践中,我认为基于垃圾收集和更改时复制语义的语言的“无共享可变存储”属性使它们更容易添加线程。最好的例子可能是 Erlang,它结合了近功能语义和显式线程。
【讨论】:
这是一个有点模糊的问题。多核 CPU 的一个好处是您可以运行一个功能程序并让它串行插入,而不必担心影响与机器正在执行的其他功能有关的任何计算。
多 U 服务器与服务器或 PC 中的多核 CPU 之间的区别在于,通过将其置于同一 BUS 上,您可以节省速度,从而可以更好、更快地与内核进行通信。
编辑:我可能应该说在我所做的大多数脚本中,无论有没有多个内核,我都很少看到通过骇人听闻的并行化获取数据的问题,例如同时运行多个小脚本我的脚本,所以我不会因为等待 URL 加载等事情而减慢速度。
双重编辑:此外,几十年来,许多函数式编程语言已经分叉了并行变体。这些更好地利用并行计算并提高了一些速度,但它们从未真正流行起来。
【讨论】:
省略任何技术/科学术语是因为函数式程序不共享数据。数据在函数之间进行复制和传输,因此应用程序中没有共享数据。
共享数据是导致多线程问题一半的原因。
【讨论】:
Joe Armstrong(Erlang 的创建者)所著的《Programming Erlang: Software for a Concurrent World》一书详细介绍了将 Erlang 用于多核(/多处理器)系统。正如维基百科文章所述:
在 Erlang 中创建和管理进程是微不足道的,而在大多数语言中线程被认为是一个复杂且容易出错的主题。尽管所有并发在 Erlang 中都是显式的,但进程使用消息传递而不是共享变量进行通信,这消除了对锁的需求。
【讨论】: