我最近来维护大量的科学计算密集型 FORTRAN 代码。尽管有 google 和两本入门级书籍,但我很难掌握一种 40 年历史的语言的所有细微差别。该代码充斥着“性能增强改进”。是否有人对将FORTRAN优化为 CS 101 级别有任何指南或实用建议?有谁知道 FORTRAN 代码优化是如何运作的?Java/C++/.NET 开发人员接管 FORTRAN 77/90 代码库时,是否有任何典型的 FORTRAN “陷阱”?
10 回答
您必须对程序员过去必须做的事情有一种“感觉”。我使用的绝大多数代码都比我老,并且运行在我父母上高中时的“新”机器上。
我处理的常见 FORTRAN 主义会损害可读性:
- 常见块
- 隐式变量
- 两个或三个带有共享 CONTINUE 语句的 DO 循环
- GOTO 代替 DO 循环
- 算术 IF 语句
- 计算的 GOTO
- 某些公共块中的等价 REAL/INTEGER/other
解决这些问题的策略包括:
- 获得Spag / plusFORT,物有所值,它自动解决了很多问题并且没有错误(tm)
- 如果可能,请移至 Fortran 90,如果不能移至自由格式的 Fortran 77
- 将 IMPLICIT NONE 添加到每个子例程,然后修复每个编译错误,虽然耗时但最终是必要的,有些程序可以自动为您执行此操作(或者您可以编写脚本)
- 将所有 COMMON 块移至 MODULE,低悬的果实,值得
- 将算术 IF 语句转换为 IF..ELSEIF..ELSE 块
- 将计算的 GOTO 转换为 SELECT CASE 块
将所有 DO 循环转换为较新的 F90 语法
myloop: do ii = 1, nloops ! do something enddo myloop
将等效的公共块成员转换为在模块中分配的 ALLOCATABLE 内存,或者如果将 Hollerith 存储在 REAL 中,则转换为它们的真实字符例程
如果您对如何完成一些可读性任务有更具体的问题,我可以提供建议。我有一个几十万行 Fortran 代码库,它是在 40 年的时间里编写的,我在某种程度上负责,所以我可能遇到过你可能发现的任何“问题”。
旧版 Fortran 肥皂盒
我帮助维护/改进了旧的 Fortran 代码库已经有一段时间了,并且在很大程度上认为六字母变量是赚钱的。不过,该建议倾向于技术性的;在实施"良好做法"方面更难解决。
- 建立所需的编码风格和编码指南。
- 需要对提交到代码库的任何内容进行代码审查(不仅仅是编码员!)。(版本控制应该与这个过程相关联。)
- 开始构建和运行单元测试;同上基准测试或回归测试。
这些天听起来似乎很明显,但冒着过度概括的风险,我声称大多数 Fortran 代码商店都有根深蒂固的文化,其中一些甚至在“软件工程”一词出现之前就开始了,随着时间的推移,什么才是主导是“立即完成”。(这绝不是 Fortran 商店所独有的。)
拥抱陷阱
但是如何处理已经存在的、陈旧的旧代码库呢?我同意乔尔·斯波尔斯基关于重写的观点,不要。但是,在我看来,sixlettervariables确实指出了允许的例外情况:使用软件工具过渡到更好的 Fortran 结构。代码分析器( FORCHECK)和代码重写器(plusFORT )可以捕获/纠正很多内容。如果您必须手动完成,请确保您有一个紧迫的理由。(我希望我手头有关于修复软件错误所产生的软件错误数量的参考资料,这令人羞愧。我认为专家 C 编程中有一些这样的统计数据。)
赢得 Fortran gotchas 游戏的最佳进攻可能就是拥有最好的防守:相当了解该语言。为此,我推荐......书籍!
Fortran 死树库
这些年来,我作为“QA nag”只取得了适度的成功,但我发现教育确实有效,有时是在不经意间,而最有影响力的东西之一就是手头上有一本参考书。我喜欢并强烈推荐
面向科学家和工程师的 Fortran 90/95,斯蒂芬·J·查普曼 (Stephen J. Chapman)
这本书甚至对 Fortran 77 也有好处,因为它专门确定了不应该使用的结构并提供了更好的替代方案。然而,它实际上是一本教科书,当你真的想了解 Fortran 95 的精髓时,它可能会失去动力,这就是我推荐的原因
Fortran 90/95 解释,Michael Metcalf 和 John K. Reid
作为 Fortran 95 的首选参考(原文如此)。请注意,这不是最清晰的写作,但是当您真正想充分利用新的 Fortran 95 功能时,面纱将会揭开。
因为专注于从 Fortran 77 到 Fortran 90 的问题,我很喜欢
迁移到 Fortran 90,作者 Jim Kerrigan
但是这本书现在已经绝版了。(我只是不明白 O'Reilly 对Safari的使用,为什么他们的绝版书没有一本?)
最后,关于精彩绝伦的经典软件工具的继承人,我提名
古典 FORTRAN , Michael Kupferschmid
这本书不仅展示了“仅”使用 Fortran 77 可以做什么,而且还讨论了出现的一些更微妙的问题(例如,应该或不应该使用 EXTERNAL 声明)。这本书与“软件工具”的内容并不完全相同,但它们是我将其标记为“有趣”的三本 Fortran 编程书籍中的两本……(这里是第三本)。
适用于几乎所有 Fortran 编译器的其他建议
- 有一个编译器选项可以强制执行 IMPLICIT NONE 行为,您可以使用它来识别问题例程,而无需先使用 IMPLICIT NONE 声明对其进行修改。直到第一次构建爆炸后,这条建议才有意义,因为 IMPLICIT NONE 命令插入到遗留例程中。(什么?你的代码审查没有发现这个?;-)
- 有一个用于数组边界检查的编译器选项,这在调试 Fortran 77 代码时很有用。
- Fortran 90 编译器应该能够编译几乎所有 Fortran 77 代码,甚至更旧的 Fortran 代码。在您的 Fortran 90 编译器上打开报告选项,通过它运行您的旧代码,您将在语法检查方面有一个不错的开始。一些商业 Fortran 77 编译器实际上是在 Fortran 77 模式下运行的 Fortran 90 编译器,因此对于您拥有的任何构建脚本来说,这可能是相对微不足道的选项。
原来的问题中有一些我要注意的地方。您说代码中充斥着“性能增强改进”。由于 Fortran 问题通常具有科学和数学性质,因此不要假设这些性能技巧是为了改进编译。这可能与语言无关。在 Fortran 中,解决方案很少涉及代码本身的效率,而是解决最终问题的基础数学。这些技巧可能会使编译变慢,甚至可能使逻辑显得混乱,但目的是使解决方案更快。除非你确切地知道它在做什么以及为什么,否则不要管它。
即使是简单的重构,比如更改看起来很笨的变量名,也可能是一个很大的陷阱。自麦克斯韦时代以来,特定科学领域的历史标准数学方程将使用特定的速记法。因此,在电磁学中看到一个名为 B(:) 的数组可以告诉所有 Emag 工程师确切的解决方案。改变它,后果自负。道德,在重命名之前了解科学的标准命名法。
作为在 FORTRAN(77 种风格,尽管我已经有一段时间没有认真使用它)和 C/C++ 方面有经验的人,需要注意的项目是数组。FORTRAN 数组从索引 1 开始,而不是像在 C/C++/Java 中那样从 0 开始。此外,存储器排列是相反的。因此,增加第一个索引会为您提供连续的内存位置。
我的妻子仍然经常使用 FORTRAN 并且有一些她需要使用的 C++ 代码,现在我将开始帮助她。当她转换过程中出现问题时,我会尝试指出它们。也许他们会有所帮助。
自 1967 年以来,我从 '66 版本开始使用 Fortran(在具有 32k 内存字的 IBM 7090 上)。然后我使用了 PL/1 一段时间,但后来又回到了 Fortran 95,因为它非常适合我们遇到的矩阵/复数问题。我想补充一点,旧代码的许多复杂结构仅仅是由于可用内存量很小,这迫使诸如通过计算或分配GOTO
的重用几行代码之类的事情。另一个问题是通过为每个重复的子表达式定义辅助变量来进行优化——编译器根本没有为此进行优化。另外,不允许写DO i=1,n+1
;你必须写n1=n+1
;DO i=1,n1
. 结果,旧代码被多余的变量所淹没。当我在 Fortran 95 中重写代码时,只有 10% 的变量幸存下来。如果您想让代码更清晰易读,我强烈建议您寻找可以轻松消除的变量。
我可能会提到的另一件事是,多年来复杂的算术和多维数组效率非常低。这就是为什么您经常发现代码被重写以仅使用实变量和使用单个线性索引寻址的矩阵进行复杂计算。
好吧,从某种意义上说,你很幸运,因为 Fortran 没有太多微妙的控制流构造或继承等方式。另一方面,它有一些真正令人惊奇的陷阱,例如算术计算的分支到数字标签的东西,不需要声明的隐式类型变量,缺少真正的关键字。
我不知道“性能增强改进”。我猜它们中的大多数可能是无效的,因为几十年的编译器技术已经使大多数暗示变得不必要了。不幸的是,除非您打算进行大规模重写,否则您可能不得不保持原样。
无论如何,核心的科学计算代码应该是相当可读的。任何使用中缀算术的编程语言都可以很好地为阅读 Fortran 的算术和赋值代码做好准备。
你能解释一下在维护代码时你必须做什么吗?你真的需要修改代码吗?如果您可以通过仅修改该代码的接口而不是代码本身来摆脱困境,那将是最好的。
处理大型科学代码(不仅仅是 FORTRAN)时的固有问题是基础数学和实现都很复杂。几乎默认情况下,实现必须包括代码优化,以便在合理的时间范围内运行。更糟糕的是,该领域的许多代码是由其领域专家而非软件开发专家的科学家/工程师创建的。让我们说“易于理解”不是他们的首要任务(我是其中之一,仍在学习成为更好的软件开发人员)。
由于问题的性质,我认为一般的问答不足以提供帮助。我建议您发布一系列附有代码片段的具体问题。也许从最让你头疼的那个开始?
我喜欢 FORTRAN,我曾经在其中教学和编码。只是想把它扔进去。好几年没碰过了。
我从 COBOL 开始,当我搬到 FORTRAN 时,我觉得我得到了自由。一切都是相对的,是吗?我会支持上面所说的 - 认识到这是一种程序语言 - 没有微妙之处 - 所以按照你的看法吧。
一开始可能会让你感到沮丧。
我从打孔卡上的 Fortran IV (WATFIV) 开始,我早期的工作是 VS FORTRAN v1(IBM,Fortran 77 级别)。在这个线程中有很多好的建议。
我要补充一点,您必须区分为使野兽完全运行所做的事情,与“优化”代码的事情,以及更具可读性和可维护性的事情。我还记得处理 VAX 覆盖时试图让 DOE 模拟代码在带有虚拟内存的 IBM 上运行(它们必须被删除并且整个东西变成一个地址空间)。
我肯定会首先仔细地将 FORTRAN IV 控制结构重构到至少 FORTRAN 77 级别,并带有适当的缩进和注释。尝试摆脱原始控制结构,例如 ASSIGN 和 COMPUTED GOTO 以及算术 IF,当然,尽可能多的 GOTO(使用 IF-THEN-ELSE-ENDIF)。绝对在每个例程中使用 IMPLICIT NONE 来强制您正确声明所有变量(您不会相信我在其他人的代码中发现了多少错误——变量名中的拼写错误)。注意“过早的优化”,最好让编译器自己处理。
如果此代码要继续存在和可维护,那么您应该为自己和您的继任者提供可读性和可理解性。更改代码时请确定您在做什么!FORTRAN 有许多奇特的结构,很容易让来自编程世界的 C 方面的人绊倒。请记住,FORTRAN 可以追溯到 50 年代中后期,当时没有语言科学和编译器设计之类的东西,只是临时将某些东西组合在一起(对不起,B 博士!)。
这是另一个不时咬我的人。当您处理 FORTRAN 代码时,请确保您跳过所有六个初始列。每隔一段时间,我只会将代码缩进五个空格,但没有任何效果。乍一看一切似乎都很好,然后我终于意识到所有的行都是从第 6 列而不是第 7 列开始的。
对于不熟悉 FORTRAN 的任何人,前 5 列用于行号(=标签),第 6 列用于连续字符,以防您的行长于 80 个字符(只需在这里放一些东西,编译器就知道这一行实际上是它之前的一部分)并且代码总是从第 7 列开始。