源作者:junziyang
fortran程序不能运行通过MATLAB的外部程序接口,可以调用Fortran或C语言程序。经MATLAB编译后,程序的扩展名为.mexw32或.mexw64,统称为MEX程序文件。虽然MATLAB的计算效率近些年来取得了很大的进步,但对于循环多、计算量大的程序,用MEX编程一般可以获得至少10倍的速度提升。特别是Fortran程序,与MATLAB有同源性,MATLAB程序比较容易转换成对应的Fortran代码。Mathworks 官方的File Exchange区,有国外的大牛分享了专门的转换工具,虽然直接转换不能保证100%成功,但可以自动完成绝大部分的代码转换,调试修改一下一般都可以成功。Fortran程序的效率一般较高,但好刀也得找到会用的人。如下本人整理和总结了一些注意事项,对提高Fortran MEX的效率或有裨益:
1. 避免使用小的整型或逻辑型数据
避免使用小于32 bit的整型或逻辑型数据。16 bit或8bit数据的访问效率较低,尤其是在IA-64架构的机器上访问效率更低。
为减少存储空间和内存开销,尽量使用32 bit型的数据,除非取值范围或精度不能满足要求时才使用64 bit型的数据。
2. 避免不同类型数据间的算术运算
参与运算的数据的类型不同时,不可避免地要进行数据类型的转换,这会造成额外的CPU开销,甚至会带来不易检查的运算错误。
例如:
REAL*8 :: A,B
B = 3*A/2 !(1) 整数3被转成REAL*8,与A相乘,然后整数2转成REAL*8,与上一步的结果相乘,最后赋给B
B = 3/2*A !(2) 3与2做整数除法,结果为整型1,将之转成REAL*8,与A相乘,然后赋给B。
B=1.5*A !(3) REAL*4型1.5转成REAL*8,与A相乘,然后赋值给B。
上面3种方法中(3)最好,避免了1次除法,需要1次类型转换;(1)需要2次类型转换;(2)最糟糕,不仅需要1次类型转换,计算结果也不对了。最佳情况应该是B=1.5D0*A
MATLAB中的默认的数值类型是双精度的,相当于Fortran中的REAL*8。因此在MATLAB上述两句都不会造成计算精度的损失。但Fortran中不同,2如果不加任何修饰,默认就是INTEGER型的。切记,切记!!
3. 避免使用计算速度慢的算术运算符
常用算术运算符的计算速度从快到慢依次如下:
(1)加(+)、减(-)、浮点数乘(*);
(2)整数乘(*);
(3)除(/);
(4)乘方(**);
现在许多编译器都会自动进行运算符的优化。
注意:只有在不影响计算精度和程序可读性的前提下,再进行此优化。
4. 避免重复计算
做计算时经常会重复使用某一个计算结果。此时应该把该结果先保存起来,再让其他算式使用。尤其是循环中要避免重复计算的出现,尽可能地把没有必要写在循环中的运算提取到循环外边。
例如:
do m=1:100
a(m)=b(m)/(c+d) !可以e=c+d,并提到循环外计算
enddo
5. 使用PARAMETER声明常量
声明成PARAMETER的常量使用时会比一般变量快。所以如果某个变量的值在程序中不会发生变化,就用PARAMETER将其声明成常量。
例如:
real F,G,M
M=1.0 !质量
G=9.8 !重力加速度
F = M*G !比较慢。
上面的G是一个物理常数,如果声明成real,parameter :: G=9.8 会比上面的做法效率高。另外声明成parameter还有个好处,如果你的程序中试图改变它的值(比如不留意起了重名变量),编译时编译器会报错。直接写到程序中的数字被视为常量,例如F=M*9.8
6. 减少程序的跳转、转向
使用流程控制命令IF、SELECT CASE、DO、WHILE、调用函数等,都会导致程序执行的跳转和转向。执行程序跟开车一样,在笔直的路上开车是最快的,遇到转弯甚至要掉头时就需要减速。所以程序中要尽量减少程序的转向和跳转。尤其是循环,能合并的尽量合并。
7. 合理利用缓存
程序执行时,如果能尽量避免跳转式的使用内存,会得到比较好的执行效率。原因在于,现在的CPU都有缓存。缓存的访问速度比内存要快得多。CPU在需要数据的时候会先检查数据是否在缓存中,没有在缓存中时才会到的内存取数据。而在从内存取数据时,除了拿回所需数据外,通常还会顺便把这笔数据邻近的几笔数据取回到缓存中。所以如果程序运行中尽量按照数据在内存中的存储顺序使用数据,会提高程序的执行效率。
例如:
do i=1:100
do j=1:100
do k=1:100
a(k,j,i)=10 !不要a(i,j,k)=10
enddo
enddo
enddo
Fortran 中的数组存储是Column Major方式排列(与C相反。与MATLAB相同,No 应该说MATLAB与之相同),所以按上面顺序循环,效率会比较高。即,即按维数由低到高的顺序循环访问数组。上面的例子仅是说明数组访问效率问题,其实可以直接a=10对整个数组赋值。
8. 留心 MATMUL
MATMUL是Fortran做矩阵相乘的intrinsic subroutine。使用这个子程序时千万要注意,不要在给它传递参数的同时做计算,而是把参数计算好再传给它。否则你可能会多付出1倍以上的代价。
例如:
C = A*B
E = matmul(C,D) !推荐这样给matmul传递参数
E = matmul(A*B,D) !虽然这样可以少声明一个变量C,但可能会付出惨重的代价
具体原理还没搞清,不过貌似传递参数时运算越复杂、参数数量越多,代价越高。尤其是循环中多次matmul时千万要避免!!
9. 尽量使用Fortran内建函数或IMSL、NAG等成熟的函数库
实践是检验真理的唯一标准
10. 尝试和比较不同编译器优化选项
磨刀不误砍柴功!
【参考文献】
[1] 彭国仑,《Fortran 95程序设计》
[2] Intel Visual Fortran Compiler Documentation