时刻

Python在科学计算中的逆势崛起

Python在科学计算中的逆势崛起
为了穿越一个未知的地方,人们可以走得更快,或者找到更好的路径。换句话说,过于关注原始性能会使你的速度变慢。软件制作也不例外。

为什么越来越多的时间紧迫的科学计算以前是用Fortran语言进行的,现在却用Python这种较慢的语言编写?

这些术语是模糊的,鼓励用户之间的部落战争,更多的是基于双方的习惯,而不是对这两种方法的客观评估。让我们试着给出一些元素,通过缩小问题的范围来达成相互理解。

“Python,一种较慢的语言”

Python一直被认为是慢的语言,也就是说,明显比Fortran、C或Rust等编译语言慢,如果你听说过这种说法,你并不例外:只要在谷歌上搜索 “为什么Python很慢”,就会有大量关于这个话题的网页。这源于Python的一个基本方面:它是一种解释型语言。这意味着每条指令都有很大的开销,因此减缓了大规模的计算,任何解释型语言都是如此,包括 Perl 或 Ruby。

所以,是的,普通 Python 比 Fortran 慢得多。

然而,这种比较是没有意义的,因为Python的科学用途并不依赖于普通的Python。相反,Python 被用作胶合层,依靠编译的优化包,它把这些包串在一起来执行所需的计算。科学计算中最广泛的软件包可能是NumPy,即Numerical Python。顾名思义,数字数据是通过这个包来操作的,而不是在普通的Python中,在幕后,所有繁重的工作都由C/C++或Fortran编译的程序来完成。从本质上讲,Python被用来将一个快速的例程指向正确的方向(即指向正确的内存地址),仅此而已。

因此,科学计算的性能评估应该以这种方式为基础。下图是一个比较的例子,显示了NumPy是如何比纯Python快2个数量级的。正如你所看到的,还存在其他的包,如果需要,可以进一步减少开销。

这使解释的方法的开销减少了2到3个数量级,使其与编译的解决方案相差无几。当然还有一些更精确的比较,请查看他们以正确的方式使用,Python比编译代码稍慢一些。

流行是可以量化的吗?一个指标是谷歌趋势,对于”fortran “和 “numpy”。Fortran确实在慢慢减少,并且在2015年已经落后于NumPy。然而,对这个数字要慎重考虑,因为更多记录的对NumPy的兴趣是由于年轻程序员和一般的Python开发者对网络资源的严重依赖而产生的偏差。

好吧,不管怎样,新手朋友喜欢Python多于Fortran。这也是由许多非科学的应用驱动的,特别是基于网络的应用。在我们的领域,这并不是指导我们决策的因素。然而,与之相关的是与HPC的关系。

“时间紧迫的科学计算”

高性能计算的软件方法之间的竞争从未像今天这样激烈。一个具体的挑战来自于高性能计算的环境,它是非常特殊的,不是一般软件进步所能优化的。举个例子,容器技术(想想Docker)在许多编程设置中已经无处不在,但在HPC的环境中,它在很大程度上还是不够的。对于语言来说也是如此,例如Rust在C/C++社区有大量的追随者,但是对于HPC来说,它的库生态系统仍然不足。

今天的HPC软件依赖于大量的方法,包括传统的单片编译代码(主要是Fortran和C++),代码生成方法(又称DSL),以及混合的解释/编译方法。后一类现在有一些严重的挑战者,如PyFR或FEniCS,它们已经被证明可以扩展到几百到几万个CPU甚至GPU。

然而据作者所知,没有一个挑战者,包括Python/HPC的尝试,已经建立了足够的社区和成熟度,以便在全球HPC领域获得优势。因此,就目前而言,把赌注押在两者身上可能更安全。

  • 使我们的Fortran/C++/C旗舰代码保持稳定。年纪大了,但更有智慧。
  • 在实际应用中尝试这些新方法中的几个(例如基于Python的)。

Python是更好的解决方案吗?

直到这一点,人们可以理所当然地认为。

Fortran非常快,而且很适合HPC平台。Python稍微慢一些,需要学习几个分层的软件包,而且不一定适合科学计算的环境。究竟为什么会有人改用Python呢?

说得好! 然而,在某些情况下,Python主义者会争辩说,”速度 “不一定要通过在不同语言之间实现相同的算法来衡量。为了掌握原因,让我们深入了解一个例子。

用户故事:同一作者,两种语言

在Cerfacs,一个昵称为Projector的工具是在2010年左右由一个只使用Fortran的程序员编写的。我们就叫他鲍勃吧。
几年后,接近2016年,鲍勃用Python重写了同一个工具。动机很简单,几年来,他的年轻同事用python完成了很多事情。同时,他们在Fortran和Tcl/Tk中的输出一直让他感到失望。尤其是Fortran中的Projector,除了Bob,几乎没有人理解,也没有人完全维护。

鲍勃认为Python是一个麻烦,一个被许多无礼的年轻人所支持的缩进的语言,他们太高兴了,他们用那些看到光明的目光说 “这不是Pythonic “来欺负别人。简而言之,他很生气…
鲍勃吃力地把这些经过多年痛苦验证的1.5K行fortran代码塞进NumPy(还有一点SciPy)。投影仪工具将燃烧器的数千个多孔投影到一个由数百万个多边形组成的三维复杂形状的表皮上。正如你可以想象的那样,这是一个相当繁重的任务。

Bob很快发现,一旦他把数据结构装入NumPy,在SciPy等软件包中就有许多预先存在的和优化过的函数,可以使他的任务更容易完成。通过搜索文档,他发现了一些有用的3D点操作工具,包括一个叫做Kdtree的智能数据表示。实现起来很容易,下午就测试了十几个投影工作流程,最后用两个scipy的Kdtree对象进行了简单的来回投影,一个用于网格表皮,一个用于钻头。通过实施,我们不应该仅仅停留在对KDtree的调用上。声明、链接、打包和分发预编译的附加库的简便性,改变了游戏规则。事实上,我们的客户在HPC集群上使用工具:我们中没有人在那里扎根,没有连接到互联网,要求安装一个新的库可能需要一些时间。(这可能解释了为什么在我们的HPC社区,”不是在这里发明的 “综合症如此频繁,但这是另一个故事)。

然后是比较粗暴的(Fortran)和基于KDtree的(Python / NumPy / SciPy)版本的时间。鲍勃选择了一个有代表性的大型测试案例。

  • 10亿元素
  • 1000万个多孔的边界节点
  • 1000万个多孔的边界节点

粗暴的版本在6小时30分钟内完成。而基于Kdtree的版本呢?只用了4分钟,几乎快了100倍,差异在零机水平上。

速度与敏捷性

这里到底发生了什么?好吧,Projector的主要操作涉及在三维点云中搜索点。Fortran版本是通过在pointcloud数组中寻找,这种搜索有O(n)O(n)的复杂性。KDTree是为这种类型的搜索设计的数据结构,其复杂度为O(log(n))O(log(n))。而当你的nn是10亿时,算法比语言性能更重要。

所以是的,对于同样的实现,Fortran会更快。但如果鲍勃坚持使用Fortran,他就不会尝试这种算法(他应该这样做,就像I. Pribec建议的那样,但他没有这样做,这就是问题的关键)。

根据定义,更高层次的语言将允许更多的探索。在鲍勃的例子中,最初的fortran版本工作得很好。没有强烈的动机去打破这个实现,插入一个定位树—octree或kdtree,看看Bob能在多大程度上减少点的模版而不降低输出。换句话说,投资回报率太不确定了。转移到python只是减少了投资部分。当然,如果事先知道最佳算法,或者人力不是问题,低级别的语言可能会更快,但在现实生活中很少有这种情况。

一位有影响力的软件工程师的一句话表明,这个问题存在于所有高级别与低级别的辩论中。

“使用高级语言的程序员比使用低级语言的程序员取得了更好的生产力和质量。像C++、Java、Smalltalk和Visual Basic这样的语言被认为在生产力、可靠性、简单性和可理解性方面比汇编和C等低级语言提高了5-15个系数(Brooks 1987, Jones 1998, Boehm 2000)。当你不需要每次在C语句做了它应该做的事情时,你就会节省时间”。- Steve McConnell

结尾

在计算机科学领域,关于执行速度与执行一项任务所需的总的程序员时间,已经说了很多。在科学计算中,有一种强烈的趋势是高估了执行时间的重要性,因此将注意力集中在速度上。

然而,正如你在任何算法课上所学到的,以及在上面的用户研究中所展示的那样,如果你没有使用足够的算法,那么速度就毫无意义。为了确保你的速度,有时放弃一点速度以获得敏捷性会有很大的帮助。
因此,当你下次要为科学计算写一个工具时,问问自己:我是否有信心能够为它选择和实现一个有效的算法?如果你有信心,编译语言会给你最好的性能。如果不是,在敏捷语言中投入一点时间来探索算法,可能是你的工具变得飞快的最好方法。

免责声明:这篇文章可能对现代Fortran很不公平,在此深表歉意。它确实集中在 “我们 “内部使用Fortran的情况上,其中有数十年之久的Legacy代码、数以百万计的非现代Fortran语句,以及令人沮丧的HPC集群规则,在那里你不是系统管理员,也没有互联网接入。读者们,请使用Fortran Discourse(https://fortran-lang.discourse.group/)了解有关现代Fortran的最新问题。

分享此文章