Features of Fortran Language to improve usability of CUDA Fortran
知っていればCUDA Fortranで便利に使えるFortran 90/95/2003の機能について
Prometech Simulation Conference 2015で発表
FORTRANコードをGPUへ移植するには,いわゆるCUDA Fortranを利用することになる.CUDA FortranではFortran 90/95 /2003等の機能を利用することができる.CUDA Fortranから利用できるFortran 90/95/2003の機能を紹介すると同時に、オブジェクト指向プログラミングを導入してCPUコードのGPU移植を簡略化した例を紹介した.
12. 支配方程式
• 1次元移流方程式
• 空間微分
• 2次精度中心差分
• 時間積分
• 1次精度Euler法
2015/9/17 Prometech Simulation Conference 2015 12
0
x
f
c
t
f
t : 時間
c : 移流速度
x : 空間方向
x
fn+1
x
fn
t c
13. プログラム作成,実行環境
• 開発環境
• Microsoft Visual Studio Community 2013
• PGI Accelerator Compiler 15.7 + CUDA 6.5
• コンパイルオプション
• ‐fast ‐Mcuda (‐McudaはGPU向けにコンパイルする場合のみ)
• 実行環境
• OS Windows 8.1
• CPU Core i7 920 (2.66GHz)
• メモリ 6GB
• GPU NVIDIA GTX Titan
2015/9/17 Prometech Simulation Conference 2015 13
14. メインルーチン
2015/9/17 Prometech Simulation Conference 2015 14
program main
use parameters
use kernel
implicit none
real(8),allocatable :: f (:)
real(8),allocatable :: d_f_dx(:)
integer :: n
allocate( f (Nx))
allocate(d_f_dx(Nx))
call initialize(f)
call output(f,"f_start.txt")
do n=1,Nt
call computeDifference(d_f_dx,f)
call integrate(f,d_f_dx)
end do
call output(f,"f_end.txt")
deallocate( f )
deallocate(d_f_dx)
end program main
program main
use parameters
use kernel
implicit none
real(8),allocatable :: f (:)
real(8),allocatable :: d_f_dx(:)
integer :: n
allocate( f (Nx))
allocate(d_f_dx(Nx))
call initialize(f)
call output(f,"f_start.txt")
do n=1,Nt
call computeDifference(d_f_dx,f)
call integrate(f,d_f_dx)
end do
call output(f,"f_end.txt")
deallocate( f )
deallocate(d_f_dx)
end program main
15. モジュール(計算パラメータ)
2015/9/17 Prometech Simulation Conference 2015 15
module parameters
implicit none
private
public :: PI2, Lx, Nx, dx, dx2, conv, dt, Nt
real(8),parameter :: PI = 3.1415926535897932384626433832795d0
real(8),parameter :: PI2 = 6.283185307179586476925286766559d0
real(8),parameter :: Lx = 1d0
integer,parameter :: Nx = 2**20
real(8),parameter :: dx = Lx/dble(Nx‐1)
real(8),parameter :: dx2 = Lx/dble(Nx‐1)*2d0
real(8),parameter :: conv = 1d0
real(8),parameter :: dt = 1d‐5
real(8),parameter :: endT = 0.5d0
integer,parameter :: Nt = int(endT/dt)
end module parameters
計算条件
計算領域 Lx = 1 m
分割数 Nx = 220(最大)
移流速度 c = 1 m/s
時間間隔 t = 10−5 s
終了時間 t = 0.5 s
16. モジュール(サブルーチン群)
2015/9/17 Prometech Simulation Conference 2015 16
module kernel
use parameters
implicit none
contains
!‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐!
subroutine initialize(f) !関数値の初期化
:
end subroutine initialize
!‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐!
subroutine computeDifference(d_f_dx,f) !空間微分
:
end subroutine computeDifference
!‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐!
subroutine integrate(f,d_f_dx) !時間積分
:
end subroutine integrate
!‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐!
subroutine output(value,filename) !ファイル出力
:
end subroutine output
!‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐!
end module kernel
17. •
関数値の初期化
2015/9/17 Prometech Simulation Conference 2015 17
subroutine initialize(f)
use parameters,only:Nx,dx,Lx,PI2
implicit none
real(8),intent(inout) :: f(Nx)
integer :: i
do i = 1,Nx
f(i) = ((1d0‐cos(PI2*dble(i‐1)*dx/Lx))/2d0)**10
end do
!配列構成子とdo反復を用いた書き方
!f = (/ ( ((1d0‐cos(PI2*dble(i‐1)*dx/Lx))/2d0)**10, i=1,Nx ) /)
end subroutine initialize
0 0.2 0.4 0.6 0.8 1
0
0.5
1
10
/2cos1
2
1
xLxxf
x
f
24. program main
use parameters
use kernel
implicit none
real(8),allocatable :: f (:)
real(8),allocatable :: d_f_dx(:)
integer :: n
allocate( f (Nx))
allocate(d_f_dx(Nx))
call initialize(f)
do n=1,Nt
call computeDifference(d_f_dx,f)
call integrate(f,d_f_dx)
end do
deallocate( f )
deallocate(d_f_dx)
end program main
メインルーチン(GPU版)
2015/9/17 Prometech Simulation Conference 2015 24
program main
use cudafor
use parameters
use kernel !モジュールを直接書き換える
implicit none
real(8),allocatable,device :: f (:) !device属性を付与してデバイス変数とする
real(8),allocatable,device :: d_f_dx(:) !
integer :: n
allocate( f (Nx)) !メモリ確保は変更無し
allocate(d_f_dx(Nx)) !
call initialize<<<Block,Threads>>>(f) !実行時の並列度の指定
do n=1,Nt
call computeDifference<<<Block,Threads>>>(d_f_dx,f)
call integrate<<<Block,Threads>>>(f,d_f_dx)
end do
deallocate( f ) !メモリ解放は変更無し
deallocate(d_f_dx) !
end program main
38. CPU版とGPU版のモジュール
CPU版 GPU版
2015/9/17 Prometech Simulation Conference 2015 38
module kernel
use parameters
implicit none
: !実行に必要なパラメータを定義
contains
!‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐!
subroutine initialize(f)
:
:
end subroutine initialize
!‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐!
:
end module kernel
module cufKernel
use cudafor
use parameters
implicit none
: !実行に必要なパラメータを定義
contains
!‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐!
attributes(global)&
subroutine initialize(f)
:
end subroutine initialize
!‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐!
:
end module cufKernel
39. 参照名を変更した手続きの呼出
2015/9/17 Prometech Simulation Conference 2015 39
program main
use parameters
use kernel ,initializeKernel=>initialize
use cufKernel,initializeKernel=>initialize
implicit none
real(8),allocatable,device :: f (:)
real(8),allocatable,device :: d_f_dx(:)
integer :: n
allocate( f (Nx))
allocate(d_f_dx(Nx))
call initializeKernel<<<Block,Threads>>>(f)
do n=1,Nt
call computeDifference<<<Block,Threads>>>(d_f_dx,f)
call integrate<<<Block,Threads>>>(f,d_f_dx)
end do
deallocate( f (Nx))
deallocate(d_f_dx(Nx))
end program main
参照名を設定することで呼
出時の手続き名を変更
参照名が重複した場合は
後で定義した方が有効
40. ポインタを利用した配列コピーの回避
2015/9/17 Prometech Simulation Conference 2015 40
program main
use parameters
use kernel
implicit none
real(8),allocatable :: f (:)
real(8),allocatable :: d_f_dx(:)
integer :: n
allocate( f (Nx))
allocate(d_f_dx(Nx))
call initialize(f)
call output(f,"f_start.txt")
do n=1,Nt
call computeDifference(d_f_dx,f)
call integrate(f,d_f_dx)
end do
call output(f,"f_end.txt")
deallocate( f )
deallocate(d_f_dx)
end program main
微分値を変数d_f_dxに書き
込み,積分の際に読み出す
ため非効率
41. 微分と積分のフュージョン
2015/9/17 Prometech Simulation Conference 2015 41
subroutine computeDifferenceAndIntegrate(fnew,f)
use parameters
implicit none
real(8),intent(out) :: fnew(Nx)
real(8),intent(in ) :: f (Nx)
real(8) :: d_f_dx
integer :: i
i=1
d_f_dx = (‐3d0*f(i)+4d0*f(i+1)‐f(i+2))/dx2
fnew(i) = f(i) ‐ conv*dt*d_f_dx
do i=2,Nx‐1
d_f_dx = (f(i+1)‐f(i‐1))/dx2
fnew(i) = f(i) ‐ conv*dt*d_f_dx
end do
i=Nx
d_f_dx = ( 3d0*f(i)‐4d0*f(i‐1)+f(i‐2))/dx2
fnew(i) = f(i) ‐ conv*dt*d_f_dx
end subroutine computeDifferenceAndIntegrate
微分を計算して直ちに積分に
利用
fに書き込むと微分値が正しく
求められないため,更新された
値を保持する変数fnewを追加
42. 微分と積分のフュージョン
2015/9/17 Prometech Simulation Conference 2015 42
program main
use parameters
use kernel
implicit none
real(8),allocatable :: f (:) !時刻nの値
real(8),allocatable :: fnew(:) !時刻n+1の値
integer :: n
allocate(f (Nx))
allocate(fnew(Nx))
call initialize(f)
do n=1,Nt
call computeDifferenceAndIntegrate(fnew, f)
f = fnew
end do
deallocate(f )
deallocate(fnew)
end program main
fnewの値をfにコピーして次の
時刻の積分に備える
配列のアドレスが交換できれば
配列の全要素のコピーを回避
57. Fortran Java C++
derived type
(派生型)
class class
component
(成分)
field data member
type‐bound
procedure
(手続き)
method
virtual member
function
用語の対応
2015/9/17 Prometech Simulation Conference 2015 57
65. 物理量を表すScalarVariable型の定義
2015/9/17 Prometech Simulation Conference 2015 65
subroutine initialize(this)
use kernel, initializeKernel=>initialize
implicit none
class(ScalarVariable) :: this
call initializeKernel(this%value%all())
end subroutine initialize
function x(this) result(d_v_dx)
use parameters,only:Nx
use kernel,computeDifferenceKernel=>computeDifference
implicit none
class(ScalarVariable) :: this
type(array),pointer :: d_v_dx
call computeDifferenceKernel(this%d_v_dx%all(),this%value%all())
d_v_dx => this%d_v_dx%getPointer()
this%d_v_dxCalculated = .true.
this%updated = .false.
end function x
初期化を行う手続き
処理の切替を容易にする
ために他のmoduleで定義
された手続きを呼出し
空間微分を
行う手続き
他のmodule
の手続きを
呼出し
66. 場を表すField型の定義
2015/9/17 Prometech Simulation Conference 2015 66
type :: Field
type(ScalarVariable),private :: f
contains
procedure,public,pass :: construct !各物理量のコンストラクタを呼び出す
procedure,public,pass :: destruct !各物理量のデストラクタを呼び出す
procedure,public, pass :: initialize
procedure,private,pass :: x
procedure,public ,pass :: t
procedure,private,pass :: update
procedure,public,pass :: assign
procedure,public,pass :: addArray
generic :: assignment(=) => assign
generic :: operator(+) => addArray
end type Field
物理量を成分として保持
(ここではfのみ)
場の初期化を行う手続き
や空間微分,時間微分を
計算する手続きを定義
実際は各物理量型の初期
化手続きや空間微分計算
の手続きを呼び出す
67. 場を表すField型の定義
2015/9/17 Prometech Simulation Conference 2015 67
function t(this) result(d_f_dt)
use parameters
use class_array
implicit none
class(Field) :: this
type(array),pointer :: d_f_dt
this%f%d_v_dt = this%x()*‐conv
d_f_dt => this%f%d_v_dt%getPointer()
this%f%d_v_dtCalculated = .true.
this%f%updated = .false.
end function t
function x(this) result(d_v_dx)
use class_array
implicit none
class(Field) :: this
type(array),pointer :: d_v_dx
d_v_dx=>this%f%x()
end function x
場の時間微分
を計算する手続き
この手続きで移流方程式
を表現
x
f
c
t
f
場の空間微分を計算
各物理量の空間微分計算
の手続きを呼び出す
68. メインルーチン
2015/9/17 Prometech Simulation Conference 2015 68
program main
use parameters
use class_Field
implicit none
type(Field) :: f
integer :: n
call f%initialize()
do n=1,Nt
print *,n
f = f + f%t()*dt
end do
end program main
書籍等に書かれているEuler法の定義と
同じ書き方ができている
69. 各派生型と手続きの呼出
2015/9/17 Prometech Simulation Conference 2015 69
Field
物理量
場の初期化
時間微分の計算
空間微分の計算
代入演算子
加算演算子
ScalarVariable
値
時間微分値
空間微分値
値の初期化
空間微分の計算
代入演算子
加算演算子
program main
use parameters
use class_Field
implicit none
type(Field) :: f
integer :: n
call f%initialize()
do n=1,Nt
print *,n
f = f + f%t()*dt
end do
end program main
array
値
代入演算子
加算演算子
乗算演算子
除算演算子
値の初期化
空間微分の計算
型の利用
手続きの呼出
70. 各派生型と手続きの呼出
2015/9/17 Prometech Simulation Conference 2015 70
Field
物理量
場の初期化
時間微分の計算
空間微分の計算
代入演算子
加算演算子
ScalarVariable
値
時間微分値
空間微分値
値の初期化
空間微分の計算
代入演算子
加算演算子
array
値
代入演算子
加算演算子
乗算演算子
除算演算子
空間微分の計算
program main
use parameters
use class_Field
implicit none
type(Field) :: f
integer :: n
call f%initialize()
do n=1,Nt
print *,n
f = f + f%t()*dt
end do
end program main
値の初期化
型の利用
手続きの呼出
71. メインルーチン(修正Euler法へ変更)
2015/9/17 Prometech Simulation Conference 2015 71
program main
use parameters
use class_Field
implicit none
type(Field) :: f
type(Field) :: f05
integer :: n
call f%initialize()
do n=1,Nt
print *,n
f05 = f + f%t()*dt
f = f + (f%t()+f05%t())/2d0*dt
end do
end program main
時間積分を修正Euler法へ変更
手続きを一切追加することなく,書籍に
書かれた式と同じ書き方で変更可能
76. ScalarVariable型の変更箇所(GPU版)
2015/9/17 Prometech Simulation Conference 2015 76
subroutine initialize(this)
use cufKernel, only:initializeKernel=>cufinitialize
use cufParameters
implicit none
class(ScalarVariable) :: this
call initializeKernel<<<Blocks, Threads>>>(this%value%all())
end subroutine initialize
function x(this) result(d_v_dx)
use parameters,only:Nx
use cufKernel,only:computeDifferenceKernel=>cufComputeDifference
use cufParameters
implicit none
class(ScalarVariable) :: this
type(array),pointer :: d_v_dx
call computeDifferenceKernel<<<Blocks, Threads>>>
(this%d_v_dx%all(),this%value%all())
d_v_dx => this%d_v_dx%getPointer()
end function x
既存(前のスライドで作成
済み)の初期化カーネルを
呼び出し
既存(前のスライドで作成
済み)の空間微分カーネル
を呼び出し
77. メインルーチン(変更無し)
2015/9/17 Prometech Simulation Conference 2015 77
program main
use parameters
use class_Field
implicit none
type(Field) :: f
integer :: n
call f%initialize()
do n=1,Nt
print *,n
f = f + f%t()*dt
end do
end program main