9

作为我博士学位的一部分。研究方面,我正在开发大气和海洋环流的数值模型。这些涉及在约 10^6 个网格点的数量级上以约 10^4 个时间步长对 PDE 系统进行数值求解。因此,在数十个 CPU 上以 MPI 运行时,典型的模型模拟需要数小时到数天才能完成。自然,尽可能提高模型效率很重要,同时确保结果字节与字节相同。

虽然我对我的 Fortran 编程感到很自在,并且知道一些使代码更高效的技巧,但我觉得仍有改进的空间,以及我不知道的技巧。

目前,我确保我使用尽可能少的除法,并尽量不使用文字常量(我很早就被教导要这样做,例如在实际计算中使用 half=0.5 而不是 0.5),使用尽可能少的超越函数尽可能等

还有哪些其他性能敏感因素?目前,我想知道一些:

1)数学运算的顺序重要吗?例如,如果我有:

a=1E-7 ; b=2E4 ; c=3E13
d=a*b*c

d会根据乘法的顺序以不同的效率进行评估吗?如今,这必须是特定于编译器的,但有直接的答案吗?我注意到 d 根据顺序(精度限制)获得(略微)不同的值,但这会影响效率吗?

2) 将大量(例如几十个)数组作为参数传递给子程序,而不是从子程序中的模块访问这些数组?

3) Fortran 95 构造(FORALL 和 WHERE)与 DO 和 IF?我知道这些在 90 年代很重要,当时代码向量化是一件大事,但是现在现代编译器能够向量化显式 DO 循环有什么不同吗?(我在工作中使用 PGI、Intel 和 IBM 编译器)

4)将数字提高到整数幂与乘法?例如:

b=a**4

或者

b=a*a*a*a

我被教导在可能的情况下总是使用后者。这会影响效率和/或精度吗?(可能也依赖于编译器)

请讨论和/或添加您知道的有关提高 Fortran 代码效率的任何技巧和提示。还有什么?如果您知道上述每个编译器与此问题相关的具体内容,请也包括在内。

补充:请注意,我本身没有任何瓶颈或性能问题。我在问是否有任何一般规则来优化操作意义上的代码。

谢谢!

4

3 回答 3

12

对不起,但你提到的所有技巧都只是......荒谬。更确切地说,它们在实践中没有任何意义。例如:

  • 使用 half(=0.5) 而不是 0.5 有什么好处?
  • 计算的同上a**4a*a*a*a. (a*a)** 2也将是另一种可能性。我个人的品味是 **4,因为一个好的编译器会自动选择最佳方式。

对于**,唯一重要的一点是 和 之间的区别a ** 4a ** 4.后者消耗更多的 CPU 时间。但是,即使在实际模拟中没有测量,这一点也毫无意义。

事实上,你的方法是错误的。尽可能地开发你的代码。之后,客观地衡量代码不同部分的成本。在没有测量之前进行优化是毫无意义的。

如果某个部分的 CPU 占用率很高,例如 50%,请不要忘记仅优化该部分不能将整个代码的成本除以两倍以上。无论如何,从最昂贵的部分(瓶颈)开始优化工作。

也不要忘记主要的改进通常来自更好的算法。

于 2011-10-15T19:05:48.943 回答
10

我赞同你所学的这些技巧在这个时代很愚蠢的建议。编译器现在为您执行此操作;这种微优化不太可能产生重大影响,并且可能不可移植。编写清晰易懂的代码。仔细选择你的算法。可以产生影响的一件事是以正确的顺序使用多维数组的索引......将 MXN 数组重铸为 NXM 可以根据程序的数据访问模式提供帮助。在此之后,如果您的程序太慢,请测量 CPU 消耗的位置并仅改进那些部分。经验表明,猜测经常是错误的,并导致编写更多不透明的代码。如果您创建一个代码部分,其中您的程序将 1% 的时间花费两倍的速度,它不会有任何区别。

以下是关于 FORALL 和 WHERE 的先前答案:如何确保我的 Fortran FORALL 构造被并行化?Fortran 95 结构,例如 WHERE、FORALL 和 SPREAD,通常会导致更快的并行代码吗?

于 2011-10-15T20:55:16.163 回答
8

你有关于做什么的先验想法,其中一些实际上可能会有所帮助,但最大的回报是在后验分析中。
补充:换句话说,a*b*c进入不同的顺序可能会节省几个周期(我怀疑),同时你不知道你没有被无缘无故花费 1000 个周期的东西蒙蔽了双眼.)

无论您多么仔细地编码,都会有您没有预见到的加速机会。这是我找到它们的方法。(有些人认为这种方法有争议)。

执行此操作时最好先关闭优化标志,这样代码就不会全部乱码。稍后您可以打开它们并让编译器完成它的工作。

让它在具有足够工作负载的调试器下运行,以便运行一段合理的时间。在它运行时,手动中断它,仔细看看它在做什么以及为什么。这样做几次,比如 10 次,这样你就不会得出关于它花费时间的错误结论。

以下是您可能会发现的示例:

  • 由于某些表达式的编码方式或使用与先前调用相同的参数值,它可能会花费大量时间不必要地调用数学库函数。
  • 它可能会花费大量时间来执行一些文件 I/O,或者打开/关闭文件,深入一些看似无害的例程调用。
  • 它可以在通用库函数中,调用从属子例程,以检查上层函数的参数标志。在这种情况下,可以通过编写一个特殊用途的函数并调用它来消除大部分时间。

如果您将整个操作执行两到三遍,您将删除在首次编写时进入任何软件的愚蠢内容。之后,您可以打开优化、并行性或其他任何东西,并确信没有时间花在愚蠢的事情上。

于 2011-10-15T21:39:19.817 回答