函数式程序设计与科学计算


42

如果这是一个模糊的问题,我深表歉意,但是这里有:

在过去的几年中,函数式编程在软件工程界引起了很多关注。许多人已经开始使用诸如Scala和Haskell之类的语言,并声称比其他编程语言和范例更成功。我的问题是:作为高性能计算/科学计算专家,我们应该对函数式编程感兴趣吗?我们应该参加这次小型革命吗?

函数式编程在SciComp工作领域中的优缺点是什么?


2
为什么要故意穿上直筒外套?副作用是一种工具;这对于实际应用至关重要。如果您想要CPU和内存效率,功能编程语言将不在我的视野内。需要自动验证/正确性检查的程序(例如,在核武器设施中使用?),那么可能会发生这种情况。
学徒队列

Answers:


34

我只做了一点函数式编程,因此请一筹莫展。

优点:

  • 函数式编程看起来非常数学。这是表达一些数学概念的很好的范例
  • 有很好的库可用于诸如程序的形式验证和定理证明之类的事情,因此可以编写对程序进行推理的程序,这对于重现性是有利的。
  • 您可以通过lambda表达式在Python和C ++中进行函数式编程。您也可以在Julia和Mathematica中进行函数式编程
  • 使用它的人并不多,因此您可以成为先锋。就像有早期采用MATLAB,Python,R以及现在的Julia一样,需要有函数编程的早期采用者才能赶上它

缺点:

  • 通常被认为是功能性编程语言的语言,例如Haskell,OCaml(和其他ML方言)和Lisp,相对于用于性能关键型科学计算的语言而言,通常被认为是缓慢的。OCaml充其量仅是C的一半左右。
  • 与计算科学中常用的语言(Fortran,C,C ++,Python)相比,这些语言缺少库基础结构。如果您想解决PDE,那么使用计算科学中较常用的一种语言比不使用一种更容易。
  • 使用函数式编程语言的计算机科学社区并不像使用过程语言的社区那么多,这意味着您将无法获得大量的学习或调试帮助,人们可能会为您提供废话。使用它(无论您是否应得)
  • 函数式编程的样式不同于过程式编程中使用的样式,过程式编程通常在计算机科学入门课程和“ MATLAB for Scientists and Engineers”类型的课程中教授

我认为“缺点”部分中的许多反对意见都可以克服。作为此Stack Exchange网站上的常见讨论点,开发人员时间比执行时间更重要。即使功能性编程语言很慢,如果可以将性能关键的部分委派给较快的过程语言,并且可以通过快速的应用程序开发证明生产率的提高,那么它们可能值得使用。在这里值得注意的是,用纯Python,纯MATLAB和纯R实现的程序比用C,C ++或Fortran进行这些相同程序的实现要慢得多。诸如Python,MATLAB和R之类的语言之所以受欢迎,正是因为它们以执行速度为代价来换取生产力,即使如此,Python和MATLAB都具有用于实现与C或C ++的已编译代码的接口的功能,因此可以将对性能至关重要的代码实现为快速执行。大多数语言都具有C的外部函数接口,足以与计算科学家感兴趣的大多数库进行接口。

您应该对函数式编程感兴趣吗?

这一切都取决于您认为什么很酷。如果您是愿意打破常规的人,并且愿意向人们传福音,那就是您想对函数式编程做任何事情的好处,我会说的坚持下去。我很想看到人们用计算科学中的函数式编程做一些很棒的事情,只要没有其他理由证明所有反对者都是错误的(并且将会有很多反对者)。如果您不是那种想和一群人打招呼的人,“为什么在地狱中使用功能性编程语言而不是(在此处插入他们最喜欢的过程编程语言)?”,那么我不会麻烦了。

函数式编程语言已用于模拟密集型工作。定量交易公司Jane Street使用OCaml进行财务建模和执行其交易策略。OCaml还用于FFTW中,以生成库中使用的某些C代码。Liszt是斯坦福大学开发的领域特定语言,并在Scala中实现,用于解决PDE。函数式编程肯定会在工业中使用(不一定在计算科学中使用);它是否会在计算科学中起飞还有待观察。


4
我想贡献一个Pro和一个Con。Pro ::代码的灵活性:由于所有内容都是一个函数,因此您始终可以通过另一个函数来调用该函数;这是非常强大的。缺点::代码易读:功能性编程代码确实很难阅读;即使是(大多数)编写它们的人。现在甚至需要花些时间来理解我为解决6个月前在Mathematica中使用B样条曲线解决一些通用PDE问题而编写的一些旧代码;当我想吓到一些同事时,我总是将代码删除;-)。
2013年

4
我唯一要添加的是:Con ::内存消耗。要消除副作用,必须进行大量复制。
马修·埃米特

1
@StefanSmith:(i)我知道它有时在研究中使用(例如,Maxima是基于Lisp的CAS);除此之外,我不知道该怎么办。(ii)不知道。我的大部分答案都是基于过去几年的对话中收集到的轶事证据。
2014年

@seb,听起来您正在描述类似Lisp的功能语言的属性,这些属性几乎不适用于类似Haskell的功能语言。
Mark S.

1
大赞@MatthewEmmett的评论。对于高性能计算而言,复制可能非常昂贵。
查尔斯

10

我对此可能有独特的见解,因为我是具有科学计算背景并且是函数式编程语言用户的HPC从业人员。我不想将HPC等同于科学计算,但是存在很多交叉点,因此这就是我回答这一问题的观点。

目前,在HPC中不太可能采用功能语言,这主要是因为HPC用户和客户真正关心的是尽可能地接近峰值性能。的确,以功能性方式编写代码时,自然会暴露出可以利用的并行性,但在HPC中还不够。并行性只是实现高性能的难题之一,您还必须考虑到广泛的微体系结构细节,并且这样做通常需要对代码的执行进行非常细粒度的控制,而这种控制在任何情况下都不可用。我所知道的功能语言。

也就是说,我寄希望于这种情况可能会改变。我注意到一种趋势,研究人员开始意识到,许多微体系结构优化可以(在一定程度上)实现自动化。这孕育了源到源编译器技术的动物园,用户在其中输入他们想要进行的计算的“规范”,并且编译器输出C或Fortran代码,以实现有效地进行必要的优化和并行处理的计算使用目标架构。顺便说一下,这就是功能语言非常适合做的事情:建模和分析编程语言。函数语言的第一个主要采用者是编译器开发者,这绝非偶然。除了一些值得注意的例外,我还没有看到它真正成立,但是想法就在那里,


8

我想在其他两个答案中增加一个方面。除了生态系统,函数式编程为并行执行(例如多线程或分布式计算)提供了绝佳的机会。它固有的不变性使其适用于并行性,这在命令式语言中通常很难解决。

由于近年来硬件性能的提高一直侧重于向处理器添加内核,而不是推动更高的频率,因此并行计算正变得越来越流行(我敢打赌大家都知道这一点)。

Geoff提到的另一件事是开发人员时间通常比执行时间更重要。我在一家构建计算密集型SaaS的公司工作,刚开始时我们进行了初步的性能测试,将C ++与Java进行了对比。我们发现C ++比Java缩短了大约50%的执行时间(这是用于计算几何的,并且数字很可能会因应用程序而异),但是我们还是选择了Java,因为开发者时间的重要性,并希望优化和未来硬件性能的改进将有助于我们将其投放市场。我可以充满信心地说,如果我们选择其他方式,我们将不会继续做生意。

好的,但是Java不是一种功能编程语言,所以您可能会问它与任何东西有什么关系。好吧,后来,当我们使用功能范例的更多支持者并偶然发现并行化的需求时,我们逐步将系统的某些部分迁移到Scala,后者将函数式编程的积极方面与命令性功能结合在一起,并与Java。当以最小的麻烦提高系统性能时,它为我们提供了极大帮助,并且当更多的内核被塞入明天的处理器中时,它可能会继续从硬件业务的进一步性能提升中受益。

请注意,我完全同意其他答案中提到的缺点,但是我认为并行执行的便利性是如此强大,以至于不能一概而论。


8

杰夫(Geoff)已经很好地概述了我除了强调他的观点之一:生态系统之外没有多大理由的原因。无论您是提倡函数式编程还是任何其他范式,您必须解决的重要问题之一就是,您必须重写许多其他软件,而其他任何软件都可以构建。示例是用于线性代数的MPI,PETSc或Trilinos或任何有限元库-全部用C或C ++编写。系统中存在着很大的惯性,也许不是因为每个人都认为C / C ++实际上是编写计算软件的最佳语言,而是因为许多人花了很多年的时间来创建对很多人。

我认为大多数计算人员都会同意,尝试新的编程语言并评估其对这个问题的适用性具有很多价值。但这将是一个艰难而孤独的时期,因为您将无法产生与其他人所做的事情竞争的结果。它也可能使您声名远扬,成为开始着手另一种编程范例的人。嘿,用C ++大约花了15年的时间来替换Fortran!


6
在这种情况下,C ++充其量只是真正替代Fortran的一半。我们一直在Fortran中看到新代码,还有很多旧代码可以启动!
比尔·巴特

2
C ++(与Fortran不同)过于复杂,无法学习和使用。新的开源科学法规仍在用Fortran编写。在我的领域(地球科学)中值得注意的是PFlotran,SPECFEM3D,GeoFEM等。对于大气科学中几乎所有新的代码,同上。恕我直言,C ++甚至都没有替换它应该替换的(C)。
stali

1
您应该尝试一下Fortran Wolfgang,这是一种很棒的语言,易于学习/编写,速度不会令您失望。
2013年

3
我不在乎速度(好吧,我做了一点,但这不是别人要考虑的首要问题)。对我而言重要的是,我需要花多长时间来编写复杂的算法,而Fortran在这方面却迷失了方向,因为该语言是如此简单。没有可说的标准库,没有允许通用代码的模板,半面向对象。Fortran根本不是我的语言,坦率地说,它也不应该适用于几乎所有其他科学计算人员。
Wolfgang Bangerth

4
@StefanSmith:是的。在科学计算中,这可能是一个可辩护的想法(我仍然会认为它已经过时且没有生产力)。就学生的教育而言,这当然不是无可辩驳的-因为我们的大多数学生都离开了学术界,并且在行业中几乎没有人使用Fortran。
Wolfgang Bangerth,2014年

7

快速总结是

  1. 数值计算使用可变性/副作用来实现其大部分加速并减少分配(许多函数式编程结构具有不变的数据)
  2. 懒惰评估可能很难与数字代码一起使用。
  3. 您正在开发一个将性能降到最低水平的程序包(C / Fortran或现在的Julia)(在这些程序包中,您还可以根据需要编辑汇编代码),或者您正在编写使用这些快速库的脚本因此,您通常倾向于关心开发时间(因此选择Julia / MATLAB / Python / R)。函数式语言往往处于一个奇怪的中间地带,这在其他学科中很有帮助,但在这里却没有帮助。
  4. 用于微分方程,优化,数值线性代数等的大多数数值算法都是根据收敛性编写/开发/证明的,即,您具有近似值并且想要获得。实现这些算法的自然风格是循环。(有些算法像一些多网格算法一样以递归方式编写,但是这种情况很少见。)x n + 1xnxn+1

这些事实使得大多数用户似乎不需要进行功能编程。


+1,但对点3的补充:我认为高级语言中的功能特性非常有用,其他答案中提到的功能语言的许多优点(例如,轻松并行化)也倾向于适用于这种情况。
Szabolcs

3

我认为有趣的是,在计算科学中使用函数式编程并不是新事物。例如,1990年的这篇论文展示了如何使用部分评估来提高用Lisp(可能是最早的函数式编程语言)编写的数字程序的性能。这项工作是GJ Sussman(SICP名望)和J Wisdom 在1992年的论文中使用的工具链的一部分,该工具链提供了太阳系混沌行为的数值证据可以在此处找到有关该计算涉及的硬件和软件的更多详细信息。


1

R是一种功能语言,也是一种统计(现在是机器学习)语言,实际上是统计的第一语言。它不是HPC语言:它不用于物理模拟等传统的“数字运算”。但是,可以使其在大型群集上运行(例如,通过MPI),以进行机器学习的大型统计模拟(MCMC)。

Mathematica也是一种功能语言,但它的核心领域是符号计算而不是数值计算。

在Julia中,您还可以采用功能样式(在过程及其面向对象的风格(多调度)之后)进行编程,但是它不是纯净的(基本数据结构都是可变的(元组除外),尽管有些库具有不可变的功能数据结构,更重要的是,它比过程样式要慢得多,因此使用不多。

我不会将Scala称为功能性语言,而是对象功能的混合体。您可以在Scala中使用许多功能概念。由于使用Spark(Spark ),Scala对云计算非常重要。(https://spark.apache.org/)。

请注意,现代Fortran实际上具有功能编程的一些元素:它具有严格的指针语义(与C不同),您可以具有纯函数(无副作用)(并标记为此类),并且可以具有不变性。它甚至具有智能索引功能,您可以在其中指定矩阵索引的条件。这类似于查询,通常只能在高级语言(如C#中的LINQ的R)中找到,或通过功能语言中的更高阶过滤器函数找到。因此,Fortran一点也不差劲,它甚至具有许多语言都没有的一些非常现代的功能(例如,协同数组)。实际上,在Fortran的未来版本中,我宁愿看到更多的功能性功能,而不是OO功能(现在通常是这种情况),因为Fortran中的OO确实笨拙且丑陋。


1

优点是每种功能语言中内置的“工具”:过滤数据非常容易,对数据进行迭代非常容易,并且为您的问题提供清晰简洁的解决方案非常容易。

唯一的缺点是,您必须开始思考这种新的思维方式:可能需要一些时间来了解自己必须知道的内容。SciComp域中的其他语言并未真正使用这些语言,这意味着您无法获得太多的支持:(

如果您对功能科学语言感兴趣,我开发了一个 https://ac1235.github.io


1

这是我为什么函数式编程可以并且应该用于计算科学的观点。好处是巨大的,缺点很快就会消失。在我看来,只有一个缺点:

精读:缺乏C / C ++ / Fortran的语言支持

至少在C ++中,这种缺点正在消失-因为C ++ 14/17添加了强大的功能来支持函数式编程。您可能需要自己编写一些库/支持代码,但是该语言将是您的朋友。例如,这是一个(警告:插件)库,它在C ++中执行不变的多维数组:https : //github.com/jzrake/ndarray-v2

另外,这是一有关C ++函数式编程的好书的链接,尽管它并不专注于科学应用。

这是我认为是专业人士的总结:

优点

  • 正确性
  • 易懂
  • 性能

正确性而言,功能程序的位置很明确:它们会迫使您正确定义物理变量的最小状态,以及使该状态随时间前进的功能:

int main()
{
    auto state = initial_condition();

    while (should_continue(state))
    {
        state = advance(state);
        side_effects(state);
    }
    return 0;
}

求解偏微分方程(或ODE)非常适合函数式编程。您只需将一个纯函数(advance)应用于当前解决方案即可生成下一个函数。

以我的经验,物理模拟软件总会受到状态管理不佳的困扰。通常,算法的每个阶段都在某个共享(有效全局)状态中运行。这使得很难或什至不可能确保正确的操作顺序,使软件容易受到可能表现为段错误的错误的影响,或更糟糕的是,错误术语不会使您的代码崩溃,但会无形中损害其科学的完整性。输出。在物理模拟中尝试管理共享状态的尝试也会抑制多线程处理,这是未来的问题,因为超级计算机正朝着更高的内核数量发展,而使用MPI进行扩展通常会达到约10万个任务。相反,由于不变性,函数式编程使共享内存并行性变得微不足道。

由于对算法进行了惰性评估,因此函数式编程的性能也得到了提高(在C ++中,这意味着在编译时生成许多类型-通常针对函数的每个应用程序生成一种类型)。但是,它减少了内存访问和分配的开销,并且消除了虚拟调度-允许编译器通过一次查看构成该算法的所有功能对象来优化整个算法。在实践中,您将试验评估点的不同排列方式(将算法结果缓存到内存缓冲区中),以优化CPU的使用与内存分配。与通常在模块或基于类的代码中看到的情况相比,由于算法阶段的局部性很高(请参见下面的示例),因此这相当容易。

只要使物理状态变得琐碎,功能程序就更容易理解。这并不是说您的所有同事都容易理解它们的语法!作者应谨慎使用命名功能,并且研究人员通常应习惯于看到功能上而非程序上表达的算法。我承认,缺乏控制结构可能会使某些人感到反感,但我不认为这会阻止我们进入能够在计算机上进行更优质科学的未来。

下面是一个示例advance函数,使用该ndarray-v2程序包从有限体积的代码改编而成。注意to_shared操作员-这些是我之前提到的评估点。

auto advance(const solution_state_t& state)
{
    auto dt = determine_time_step_size(state);
    auto du = state.u
    | divide(state.vertices | volume_from_vertices)
    | nd::map(recover_primitive)
    | extrapolate_boundary_on_axis(0)
    | nd::to_shared()
    | compute_intercell_flux(0)
    | nd::to_shared()
    | nd::difference_on_axis(0)
    | nd::multiply(-dt * mara::make_area(1.0));

    return solution_state_t {
        state.time + dt,
        state.iteration + 1,
        state.vertices,
        state.u + du | nd::to_shared() };
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.