通常,函数式编程不能使程序更快。它的目的是简化并行和并发编程。有两个主要键:
- 避免可变状态往往会减少程序中可能出错的事物的数量,在并发程序中甚至更是如此。
- 避免使用共享内存和基于锁的同步原语,而使用高级概念,往往会简化代码线程之间的同步。
关于点2的一个很好的例子是,在Haskell中,我们在确定性并行与非确定性并发之间有着明显的区别。没有比引用西蒙·马洛(Simon Marlow)出色的著作《Haskell中的并行和并发编程》更好的解释了(引自第1章):
甲并行程序是一种使用计算硬件(例如,若干个处理器核心)的多个更快速地执行计算。目的是通过将计算的不同部分委派给同时执行的不同处理器,从而尽早获得答案。
相反,并发是一种程序结构技术,其中有多个控制线程。从概念上讲,控制线程“同时”执行;即,用户看到了他们的效果交错。它们是否实际上同时执行是实现细节。并发程序可以通过交错执行在单个处理器上执行,也可以在多个物理处理器上执行。
除此之外,Marlow还提到了确定性的维度:
相关的区别是之间的确定性和不确定性的编程模型。确定性编程模型是其中每个程序只能给出一个结果的模型,而非确定性编程模型则根据执行的某些方面允许可能具有不同结果的程序。并发编程模型不一定是确定性的,因为它们必须与在无法预测的时间导致事件的外部代理进行交互。但是,不确定性有一些明显的缺点:程序变得更加难以测试和推理。
对于并行编程,我们将尽可能使用确定性编程模型。由于目标只是为了更快地找到答案,因此我们宁愿不要使我们的程序在此过程中难以调试。确定性并行编程是两全其美的方法:可以在顺序程序上执行测试,调试和推理,但是在增加更多处理器的情况下,程序运行速度更快。
在Haskell中,围绕这些概念设计了并行性和并发功能。特别是,与其他语言归为一个功能集一样,Haskell分为两种:
如果您只是想加速纯粹的确定性计算,那么具有确定性并行性通常会使事情变得容易得多。通常,您只需要执行以下操作:
- 编写一个产生答案列表的函数,每个答案的计算成本都很高,但彼此之间并不太依赖。这是Haskell,因此列表是惰性的 -直到消费者要求它们时才真正计算其元素的值。
- 使用Strategies库可以跨多个内核并行使用函数的结果列表元素。
几周前,我实际上是通过我的一个玩具项目程序做到这一点的。并行化程序是微不足道的-实际上,我要做的关键是添加一些代码,指出“并行计算此列表的元素”(第90行),并且在我一些较昂贵的测试用例。
我的程序是否比使用传统的基于锁的多线程实用程序要快?我非常怀疑。就我而言,整洁的事情是花了这么少的钱—我的代码可能不是很理想,但是因为它是如此容易并行化,所以与正确地进行性能分析和优化相比,我以更少的精力获得了很大的加速,而且没有比赛条件的风险。我想这就是函数式编程允许您编写“更快”的程序的主要方式。