作爲一隻CFD小白,Fortran是無論如何都繞不開的語言,無數已有的程序甚至小規模的商用軟件都是Fortran寫的。但是這些古老的代碼存在goto過多,format使用過多,大量的矩陣操作都是用do循環堆砌,以及變量命名過度簡化,fixed格式導致排版亂七八糟等等問題。總之就是可讀性差,複用困難。正好疫情期間躲在家,在改寫已有的組內代碼的同時,也將原來的.f程序升級成爲.f90程序。爲此學習fortran語言的書《Fortran 95 程序設計》 彭國倫著。
因爲已經使用過一年左右的Fortran語言,主要關注點是之前不怎麼使用的語法和功能,尤其是從F77升級到F90和F95之後的新增功能。對於已經比較熟悉的部分就會略過,只挑覺得重要,或寫高性能計算程序時有用的部分說。作爲這段時間改寫程序的一個記錄。
OK!那我們開始吧!
第一章 計算機概論
略,高性能計算和CPU運算的一些細節確實相關,不過最好參考更加專門的書籍,比如說《計算機體系結構》胡偉武
第二章 編譯器的使用
win下的編譯器比較常用的是Visual Fortran和VS之下的Fortran組件。筆者主要使用的是linux的編譯器。Linux下的GNU會包含F77的編譯器G77,編譯方式如下
g77 hello.f
./a.out
F90則使用另外一個編譯命令:
f90 hello.f90
而F95則使用:
F hello.f95
呃,不過現在我在用的是gfortran
,書中並未介紹。正常編譯.f90是可以通過的,等到遇到問題再回到這裏補充吧。
第三章 Fortran程序設計基礎
Fixed 和 Free 格式
在Fortran77的時候,使用的是Fixed Format固定格式。而在Fortran90以及95,則使用Free Format自由格式。兩者的主要規則羅列如下:
Fixed Format:
- 每行第7到72個字符有效,會被編譯。前六個字符一般爲空格
- 每行第5個字符如果是空格或者0之外的字符,就是續航符
- 每行前幾個字符可以用來給定行標,方便format,goto等等命令使用
- 使用小寫字母c註釋
Free Format: - 行首的六個空格被取消,可編寫字符擴展到132個
- 續航符爲每行尾或者行首的&符號
- 行標寫在每行最前面即可
- 註釋用!而不是c
Fortran的數據類型和數學表達式
相對F77沒有新知識
第四章 輸入輸出聲明
輸出
輸出使用如下兩種:
write(*,*) "String"
print *,"Hello"
其中F90使用雙引號,F77使用單引號。輸出特殊字符的細節此處不說明,write
的左右*星號分別代表輸出到那個端口,以及輸出格式。而print
只能控制輸出格式,輸出一定是輸出到終端的。
變量的聲明與kind
基礎的變量聲明沒什麼好說的,有一個新知識是聲明使用的變量的bytes數
integer*4 a !F77
integer(kind=4) a !F90
real*8 b !F77
real(kind=8) b !F90
其中kind
的值代表存儲變量使用的bytes
數量,上述分別代表長整型和雙精度。而F90的聲明可以向下兼容。其中變量某個值所需的kind值,可以通過fortran命令給出,具體需要查閱書中Page67
變量的賦值精度
爲什麼專門強調這個問題,在科學計算裏面,有一些基礎參數的設定一定要儘可能的精確。比如說測試數值格式精度,誤差會下降到1e-10的情況下,如果計算區域長度的設置本來是real*8 :: a=1.0
實際賦值之後的變量爲a=1.000008
,即單浮點數的精度之後,出現了誤差,那麼整體精度就不可能達到1e-10。
書中給出,給單精度浮點數賦值時,使用的是a=1.23E3
。而給雙精度浮點數賦值時,使用的是a=1.23D3
。部分編譯器即使是給雙精度的變量賦值,也一定要帶D
纔能有足夠精度的賦值。
輸入
就是read
命令,這裏介紹的都很基礎,略。
format格式
基礎用法如下:
write(*,100) a
100 format(I4)
要用到行號,書中給出了格式詳細的參數說明,不過經常使用的是I,F,E,A,X這幾個。詳細的說明可以直接翻書或者百度,這裏只簡單說明羅列:
!整數I
write(*,"(I5)") 100 !5個字符寬的輸出
write(*,"(I5.3)") 10 !5個字符寬的輸出,最少輸出3個數字
!浮點數E和D分別爲單雙精度
write(*,"(E15.7)") 123.45 !佔15個字符,小數佔7位
write(*,"(E9.2E3)") 12.34 !佔9個字符,小數佔2位,指數部分輸出三個數字
!字符串A
write(*,"(A10)") "Hello" !10個字符寬
!移位X
write(*,"(5X,I3)") 100 !輸出3個字符寬,同時先填5個空格
implicit命令
常用的就兩個
implicit real*8 (a-h,o-z)
implicit none
不寫的話,fortran也會默認第一行的情形,不過是單精度。不過比較推薦第二種,不然師兄的程序傳給師弟的時候,師弟看得頭髮都快掉沒了。
parameter參數
使用方法如下:
real,parameter :: a=1.0d0
real a
parameter (a=1.0d0)
賦初值與雙冒號
聲明的同時賦初值的話,需要使用雙冒號::
,即
real :: a=1.0d0
也可以用Data進行賦初值,不過這裏並不方便,不說明
等價聲明
新知識,類似C語言裏面的&a=b
,這裏對應
integer a,b
equivalence(a,b)
聲明後,a和b使用同一個內存空間。主要作用是調用一些高維數組中的單個元素,因爲數組下標索引時會帶來額外的運算量。
equivalence (a(1,1,5),b)
聲明在程序中的作用
這部分對於高性能計算還是比較重要的。首先對於編程來說,聲明本身一定要出現在數值計算之後。對於數值計算來說,聲明的部分在代碼編譯之後,編譯器會預留空間給程序使用
Fortran中的結構體type功能
給一個實例:
!結構體定義
type: person
integer :: age
integer :: length
integer :: weight
end type person
!結構體聲明和賦初值
type(person) :: a=person(20,170,60)
!結構體的使用
a%age = 12
第五章 流程控制與邏輯運算
就是if
和select case
命令。其中判斷語句如果對字符串使用,字符串會轉換成數字進行比較,例如"abc"<"abcd"
。然後給出一個select case
的例子
select case(score)
case(60)
a=1
case(:59)
a=0
case(61:100)
a=2
case(101:)
a=3
case default
a=4
end select
pause,continue,stop
pause
代表暫停,直到用戶按下Enter鍵。continue
功能就是繼續向下執行程序,只是方便閱讀。stop
是終止程序運行。
第六章 循環
do
循環的循環體有多種寫法:
do i = 1,10
a=a+i
end do
do 100 i=1,10
100 a=a+i
do 100 i=1,10
a = a+i
100 continue
cycle和exit
前者爲直接進行下一次循環,後者爲跳出當前循環
循環的署名
新知識,例子如下:
outter: do i = 1,10
inner: do j = 1,10
....
end do inner
end do outter
第七章 數組
基本知識不提了,這裏Fortran有一個其他語言中數組沒有的功能,尤其是CFD中需要使用ghost point時非常方便的。
real a(-3:8)
即,下標起始位置可以人爲規定,當然默認是1。
數組賦初值有以下幾種技巧,其中隱含式循環爲之前沒用過的新知識:
integer A(5)
DATA A /1,2,3,4,5/
integer b(5)
DATA (B(I),I=2,4) /2,3,4/
integer c(5) = (/1,(2,I=2,4),5/)
對整個數組的操作
假設有三個維數相同的數組a,b,c,那麼數組的操作和Matlab中的數組操作會非常類似。這是F90之後的新功能,也是Fortran適合科學計算的一個原因。
a = 5 !全部賦值爲5
a=/(1,2,3)/ !數組前三個元素分別賦值1,2,3
a=b !b的元素分別賦值給a
a=b+c
a=b-c
a=b*c
a=b/c
a=sin(b) !對數組的元素分別進行四則運算或者函數運算
甚至還可以像matlab一樣非常靈活的只對數組中其中一部分元素進行操作。
a(3:5)=5
a(3:)=5
a(3:5)=(/3,4,5/)
a(1:3) = b(4:6)
a(1:5:2) = 3
a(1:10) = a(10:1:-1)
a(:) = b(:,2)
a(:,:) = b(:,:,1)
除此之外,F95還提供了WHERE功能和FORALL功能,能夠自動檢索並進行多元素操作。和matlab中功能也類似
where (a<3)
b = a
end where
forall (i=1:size,j=1:size,i>j) a(i,j) = 1
forall (i=1:size,j=1:size,i==j) a(i,j) = 2
forall (i=1:size,j=1:size,i<j) a(i,j) = 3
數組的保存規則
Fortran中數組的保存規則和C語言恰好是相反的比如
integer a(2,3,4,5)
Fortran爲下標從左向右,先跑最左側下標。而C語言爲從右向左。另外,當fortran運行時,索引數組中的一個元素,是從首地址開始,根據下標計算元素所在地址。所以維數很高的數組的元素調用會有較大的額外運算量。
另外,CPU在運行時,在地址相鄰的位置調用元素可以大大提高cache的命中率,從而提高CPU運行速度。這就要求我們能夠對數組下標的順序進行調整。例如
do i = 1,5
sum = sum + a(1,1,1,1,i)
end do
do i = 1,n
do j = 1,m
a(i,j) = ...
end do
end do
就不如
do i = 1,5
sum = sum + a(i,1,1,1,1)
end do
do i = 1,n
do j = 1,m
a(j,i) = ...
end do
end do
可變大小數組
先給出用法
integer,allocatable :: a(:,:)
if (.not. allocated(a)) then
allocate(a(-1:5,5),stat=error)
end if
a(1,1) = 1
deallocate(a)
這裏說明分配內存是動態的,會損失一部分運行速度,但是並不會損失特別多,是比較推薦的一種寫法。而stat=error
是用來反饋是否分配內存成功,可以不寫。此時動態分配的內存和C類似,是必須釋放的。而if條件可以判斷a是否已經被分配內存。