函数式编程和多核架构
我在某处读到,函数式编程适合利用计算中的多核趋势。 我真的不明白。 它与 lambda 演算和冯诺依曼架构有关吗?
I've read somewhere that functional programming is suitable to take advantage of multi-core trend in computing. I didn't really get the idea. Is it related to the lambda calculus and von neumann architecture?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
函数式编程最大限度地减少或消除了副作用,因此更适合分布式编程。 即多核处理。
换句话说,许多难题可以同时在不同的核心上独立解决,而不必担心一个操作会影响另一个操作,就像在其他编程风格中一样。
Functional programming minimizes or eliminates side effects and thus is better suited to distributed programming. i.e. multicore processing.
In other words, lots of pieces of the puzzle can be solved independently on separate cores simultaneously without having to worry about one operation affecting another nearly as much as you would in other programming styles.
处理并行处理最困难的事情之一是锁定数据结构以防止损坏。 如果两个线程在没有完全锁定数据结构的情况下同时改变数据结构,则可能会导致从无效数据到死锁的任何情况。
相反,函数式编程语言倾向于强调不可变数据。 任何状态都与逻辑分离,数据结构一旦创建就无法修改。 锁定的需要大大减少。
另一个好处是,一些非常容易并行化的过程(例如迭代)被抽象为函数。 在 C++ 中,您可能有一个 for 循环,对列表中的每个项目运行一些数据处理。 但是编译器无法知道这些操作是否可以安全地并行运行——也许其中一个操作的结果取决于它之前的操作。 当使用像
map()
或reduce()
这样的函数时,编译器可以知道调用之间不存在依赖关系。 因此可以同时处理多个项目。One of the hardest things about dealing with parallel processing is locking data structures to prevent corruption. If two threads were to mutate a data structure at once without having it locked perfectly, anything from invalid data to a deadlock could result.
In contrast, functional programming languages tend to emphasize immutable data. Any state is kept separate from the logic, and once a data structure is created it cannot be modified. The need for locking is greatly reduced.
Another benefit is that some processes that parallelize very easily, like iteration, are abstracted to functions. In C++, You might have a for loop that runs some data processing over each item in a list. But the compiler has no way of knowing if those operations may be safely run in parallel -- maybe the result of one depends on the one before it. When a function like
map()
orreduce()
is used, the compiler can know that there is no dependency between calls. Multiple items can thus be processed at the same time.您引用的信念背后的论点是,纯函数式编程控制副作用,这使得引入并行性变得更加容易和安全,因此,纯函数式编程语言在多核计算机的上下文中应该是有利的。
不幸的是,由于以下几个原因,这种信念早已被证明是错误的:
纯函数式数据结构的绝对性能很差。 因此,在性能方面(这是并行编程的唯一目的),纯函数式编程是朝着错误方向迈出的一大第一步。
纯函数式数据结构的扩展性很差,因为它们强调共享资源,包括分配器/GC 和主内存带宽。 因此,随着核心数量的增加,并行化的纯函数式程序通常获得的加速效果很差。
纯函数式编程导致性能不可预测。 因此,真正的纯函数式程序在并行化时通常会出现性能下降,因为粒度实际上是随机的。
例如,Haskell 社区经常引用的混蛋两行快速排序通常会运行数千次比用更传统的语言(如 F#)编写的真正的就地快速排序慢几倍。 此外,虽然您可以轻松地并行化优雅的 Haskell 程序,但您不太可能看到任何性能改进,因为所有不必要的复制都会使单个核心饱和多核机器的整个主内存带宽,从而使并行性变得毫无价值。 事实上,没有人能够在 Haskell 中编写任何类型的具有竞争力的性能的通用并行排序。 Haskell 标准库提供的最先进的排序通常比传统的替代方案慢数百倍。
然而,函数式编程更常见的定义是一种强调使用一流函数的风格,实际上在多核编程环境中非常有用,因为这种范例非常适合分解并行程序。 例如,请参阅 .NET 4 中
System.Threading.Tasks
命名空间中新的高阶Parallel.For
函数。The argument behind the belief you quoted is that purely functional programming controls side effects which makes it much easier and safer to introduce parallelism and, therefore, that purely functional programming languages should be advantageous in the context of multicore computers.
Unfortunately, this belief was long since disproven for several reasons:
The absolute performance of purely functional data structures is poor. So purely functional programming is a big initial step in the wrong direction in the context of performance (which is the sole purpose of parallel programming).
Purely functional data structures scale badly because they stress shared resources including the allocator/GC and main memory bandwidth. So parallelized purely functional programs often obtain poor speedups as the number of cores increases.
Purely functional programming renders performance unpredictable. So real purely functional programs often see performance degradation when parallelized because granularity is effectively random.
For example, the bastardized two-line quicksort often cited by the Haskell community typically runs thousands of times slower than a real in-place quicksort written in a more conventional language like F#. Moreover, although you can easily parallelize the elegant Haskell program, you are unlikely to see any performance improvement whatsoever because all of the unnecessary copying makes a single core saturate the entire main memory bandwidth of a multicore machine, rendering parallelism worthless. In fact, nobody has ever managed to write any kind of generic parallel sort in Haskell that is competitively performant. The state-of-the-art sorts provided by Haskell's standard library are typically hundreds of times slower than conventional alternatives.
However, the more common definition of functional programming as a style that emphasizes the use of first-class functions does actually turn out to be very useful in the context of multicore programming because this paradigm is ideal for factoring parallel programs. For example, see the new higher-order
Parallel.For
function from theSystem.Threading.Tasks
namespace in .NET 4.当没有副作用时,评估顺序并不重要。 然后可以并行计算表达式。
When there are no side effects the order of evaluation does not matter. It is then possible to evaluate expressions in parallel.
基本论点是,像 C/C++ 等语言很难自动并行化,因为函数可以设置全局变量。 考虑两个函数调用:
虽然 foo 和 bar 没有共同的参数,并且一个不依赖于另一个的返回代码,但它们仍然可能具有依赖关系,因为 foo 可能会设置 bar 所依赖的全局变量(或其他副作用)。
函数式语言保证 foo 和 bar 是独立的:没有全局变量,也没有副作用。 因此 foo 和 bar 可以在不同的内核上自动安全地运行,无需程序员干预。
The basic argument is that it is difficult to automatically parallelize languages like C/C++/etc because functions can set global variables. Consider two function calls:
Though foo and bar have no arguments in common and one does not depend on the return code of the other, they nonetheless might have dependencies because foo might set a global variable (or other side effect) which bar depends upon.
Functional languages guarantee that foo and bar are independant: there are no globals, and no side effects. Therefore foo and bar could be safely run on different cores, automatically, without programmer intervention.
上面的所有答案都指向一个关键思想:“无共享可变存储”是并行执行程序片段的关键推动因素。 它并没有真正解决寻找并行执行的同样困难的问题。 但函数式语言中典型的更清晰的功能表达确实使得理论上更容易从顺序表达式中提取并行性。
在实践中,我认为基于垃圾收集和更改时复制语义的语言的“无共享可变存储”属性使它们更容易添加线程。 最好的例子可能是 Erlang,它将近乎功能的语义与显式线程相结合。
All the answers above go to the key idea that "no shared mutable storage" is a key enabler to execute pieces of a program in parallel. It does not really solve the equally hard problem of finding things to execute in parallel. But the typical clearer expressions of functionality in functional languages do make it theoretically easier to extract parallelism from a sequential expression.
In practice, I think the "no shared mutable storage" property of languages based on garbage collection and copy-on-change semantics make them easier to add threads to. The best example is probably Erlang, that combines near-functional semantics with explicit threads.
这是一个有点模糊的问题。 多核 CPU 的好处之一是,您可以运行一个功能程序并让它串行插入,而不必担心影响与机器正在执行的其他功能有关的任何计算。
多 U 服务器与服务器或 PC 中的多核 CPU 之间的区别在于,通过将其置于同一总线上,可以节省速度,从而可以更好、更快地与内核进行通信。
编辑:我可能应该通过说在我所做的大多数脚本中,无论有或没有多核,我很少看到通过黑客并行化获取数据的问题,例如在我的脚本中一次运行多个小脚本,所以我不会因为等待 URL 加载之类的事情而放慢速度。
双重编辑:此外,几十年来,许多函数式编程语言都已经产生了并行变体。 这些更好地利用了并行计算,并提高了一些速度,但它们从未真正流行起来。
This is a little bit of a vague question. One perk of multi-core CPUs is that you can run a functional program and let it plug away serially without worrying about affecting any computing going on that has to do with other functions the machine is carrying out.
The difference between a multi-U server and a multi-core CPU in a server or PC is the speed savings you get by having it on the same BUS, allowing better and faster communication to the cores.
edit: I should probably qualify this post by saying that in most of the scripting I do, with or without multiple cores, I rarely see a problem in getting my data through hackish parallelizing, such as running multiple small scripts at once in my script so I'm not slowed down by things like waiting for URLs to load and what not.
double edit: Furthermore, a lot of functional programming languages have had forked parallel variants for decades. These better utilize parallel computation with some speed improvement, but they never really caught on.
省略任何技术/科学术语的原因是因为函数式程序不共享数据。 数据在函数之间复制和传输,因此应用程序中不存在共享数据。
共享数据是导致多线程问题的一半原因。
Omitting any technical/scientific terms the reason is because functional program doesn't share data. Data is copied and transfered among functions, thus there is no shared data in the application.
And shared data is what causes half the headaches with multithreading.
Joe Armstrong(作者)的Programming Erlang: Software for a Concurrent World一书Erlang) 讨论了很多关于使用 Erlang 用于多核(/多处理器)系统。 正如维基百科文章所述:
The book Programming Erlang: Software for a Concurrent World by Joe Armstrong (the creator of Erlang) talks quite a bit about using Erlang for multicore(/multiprocessor) systems. As the wikipedia article states: