有关提高Fortran代码性能的提示和技巧

时间:2011-10-15 18:15:39

标签: performance fortran hpc

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

虽然我觉得我的Fortran编程非常舒服,并且我意识到使代码更高效的一些技巧,但我觉得还有待改进的空间,以及我不知道的技巧。

目前,我确保尽可能少地使用分区,并尽量不使用文字常量(我很早就教会这样做,例如在实际计算中使用half = 0.5而不是0.5),用作尽可能少的超越功能等。

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

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

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

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

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

3)Fortran 95构造(FORALL和WHERE)与DO和IF?我知道这些问题在90年代很重要,因为代码矢量化是一件大事,但是现在的编译器能否对显式DO循环进行矢量化有什么不同吗? (我在工作中使用PGI,Intel和IBM编译器)

4)将数字提高到整数倍与乘法相比? E.g:

b=a**4

b=a*a*a*a

我被教导要尽可能使用后者。这会影响效率和/或精度吗? (可能也是编译器依赖)

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

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

谢谢!

3 个答案:

答案 0 :(得分:11)

很抱歉,但你提到的所有技巧都是......太荒谬了。更确切地说,它们在实践中没有任何意义。例如:

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

对于**,唯一可能重要的点是a ** 4a ** 4.之间的差异,后者的CPU耗时更多。但是,如果没有实际模拟中的测量,即使这一点也没有意义。

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

如果某个部件占CPU的百分比很高,例如50%,请不要忘记优化该部件只能将整个代码的成本除以大于2的因子。无论如何,通过最昂贵的部分(瓶颈)开始优化工作。

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

答案 1 :(得分:7)

我接下来的建议是,在这个时代,你所教授的这些技巧是愚蠢的。编译器现在为您执行此操作;这种微观优化不太可能产生显着差异,可能无法移植。写清楚&可理解的代码。仔细选择您的算法。可以产生影响的一件事是以正确的顺序使用多维数组的索引...将N X M重新编码为N X M可以帮助您根据程序的数据访问模式。在此之后,如果您的程序太慢,请测量CPU的消耗位置并仅改善这些部分。经验表明,猜测经常是错误的,并导致编写更多不透明的代码,也没有理由。如果你制作一个代码部分,你的程序花费的时间是其时间的两倍,那么它将没有任何区别。

以上是FORALL和WHERE的先前答案:How can I ensure that my Fortran FORALL construct is being parallelized?Do Fortran 95 constructs such as WHERE, FORALL and SPREAD generally result in faster parallel code?

答案 2 :(得分:5)

你有关于该做什么的先验想法,其中一些可能实际上有所帮助, 但最大的回报是在后验分析中 (已添加:换句话说,将a*b*c置于不同的顺序可能会节省几个周期(我怀疑),而与此同时你不知道你没有得到由于没有充分理由花费1000个周期的事情而盲目地支持。)

无论您如何仔细编码,都会有机会加速,而您却没有预见到。这是我找到它们的方式。 (有些人认为this method存在争议。

最好在执行此操作时从优化标志OFF开始,因此代码不会全部加扰。 稍后你可以打开它们让编译器做它的事情。

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

以下是您可能会找到的事项示例:

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

如果你完成整个操作两三次,你将删除在第一次写入时进入任何软件的愚蠢内容。 在那之后,你可以打开优化,并行性等等,并且相信没有时间花在愚蠢的事情上。

相关问题