- 追加された行はこの色です。
- 削除された行はこの色です。
// 現在(&lastmod;)作成中です。
// 既に書いている内容も&color(#ff0000){大幅に変わる};可能性が高いので、注意。
-------
#contents
-------
* 担当教員 [#oa368592]
臼井 英之
谷口 隆晴
* 演習日 [#d56788cd]
- 2010.06.03
- 2010.06.10
- 2015.05.21
- 2015.05.28
* はじめに [#m9e58d5d]
* 概要と達成目標 [#pc607f98]
OpenMPを用いた,共有メモリ型並列計算機上のプログラミングの基礎を学ぶ.
** 特徴 [#rd78ca67]
OpenMPを用いた並列計算の基礎
(共有メモリ型計算機上での並列処理を実現)
* 講義資料 [#z2e2e767]
- 準備中
//- 2014/05/29 講義資料 &attachref(lecture20140529.pdf);
//- 2014/06/05 講義資料 &attachref(lecture20140605.pdf);
** 目標 [#f9334b91]
//* Xサーバの有効にしてログインする方法 [#bb240a5e]
//+ すべてのプログラム → Xming → Xming
//+ Tera Term を起動して最初のダイアログで「キャンセル」
//+ Tera Term で「設定」→「SSH転送」→「リモートの(X)アプリケーションをローカルのXサーバに表示する」にチェック
//+ 「ファイル」→「新しい接続」でログイン
まずは、Doループを簡単に並列化する。
(但し、ノードをまたがない。)
//* アンケート [#j36d6593]
//今日の講義(6月5日)はどうでしたか?1人1回,&color(red){''必ず''};回答して下さい.
OpenMPの指示行の入ったプログラムを並列マシンで走らせる。
//難易度
//#vote(簡単すぎた[0], ちょうどよかった[8], 少し難しかった[2], 難しすぎた[0])
//分量
//#vote(少ない[1], ちょうどよい[8], 少し多い[2], 多すぎる[0])
//今日の講義(5月29日)はどうでしたか?1人1回,&color(red){''必ず''};回答して下さい.
K&Rに出てくるC言語でのhello, worldプログラムは、
#include <stdio.h>
main () {
printf("hello, world.\n");
}
である。
//難易度
//#vote(簡単すぎた[0], ちょうどよかった[16], 少し難しかった[2], 難しすぎた[0])
Fortran90/95ではこうなる。
program hello_world
implicit none
print *, "hello, world."
end program hello_world
//分量
//#vote(少ない[0], ちょうどよい[16], 少し多い[2], 多すぎる[0])
このようにほとんど同じである。
いくつかの細かい違いは、
* &color(#0000ff){【演習】}; [#ic0d822f]
以下のプログラムをmatmul_a_transpose_b.f95というファイルに保存し、
コンパイル&実行せよ。
program matmul_a_transpose_b
implicit none
real, dimension(10,10) :: A, B, C, D
A = 1.0 ! A(i,j) = 1 for all i and j.
B = 2.0 ! B(i,j) = 2
D = 3.0 ! D(i,j) = 3
C = matmul(transpose(A),B) + D
print *, 'max element of C is ', maxval(C)
end program matmul_a_transpose_b
* サンプルプログラム: 級数 [#p14d8a7b]
級数
#ref(series_one_forth.jpg)
// \sum_{n=1}^\infty\,\frac{1}{i}\cdot\frac{1}{i+1}\cdot\frac{1}{i+2} = \frac{1}{1}\cdot\frac{1}{2}\cdot\frac{1}{3} + \frac{1}{2}\cdot\frac{1}{3}\cdot\frac{1}{4} + \frac{1}{3}\cdot\frac{1}{4}\cdot\frac{1}{5} + \cdots = \frac{1}{4}
の最初の1000項の和を求めるプログラムも、式をそのまま書けばいい。まさにFormula Translation。
program series_one_forth
implicit none
integer, parameter :: nterms = 1000
real, dimension(nterms) :: x, y, z
integer :: i
do i = 1 , nterms
x(i) = 1.0 / i
y(i) = 1.0 / (i+1)
z(i) = 1.0 / (i+2)
end do
print *,'ans = ', sum(x*y*z)
end program series_one_forth
* &color(#0000ff){【演習】}; [#nedf31cf]
上のプログラムを series_one_forth.f95という名前に保存し、
コンパイル&実行せよ。
* 部分配列 [#edc447de]
上の例では第1行目から第1000項目までの和をとっていた。
もしも第50項目から第90行目までの和が欲しければ以下のようにすればよい。
program series_one_forth_02
implicit none
integer, parameter :: nterms = 1000
real, dimension(nterms) :: x, y, z
integer :: i
do i = 1 , nterms
x(i) = 1.0 / i
y(i) = 1.0 / (i+1)
z(i) = 1.0 / (i+2)
end do
print *,'ans02 = ', sum(x(50:90)*y(50:90)*z(50:90))
end program series_one_forth_02
ここに出てきた x(50:90)などは部分配列と呼ぶ。
部分配列も含めてFortran90/95には配列を扱うための便利な機能が多数用意されている。
配列機能については次回詳しく解説する。
* サンプルプログラム: 3次元ベクトル場の全エネルギー [#fe1188c3]
空間中に分布する磁場(3成分ベクトル場)の全磁気エネルギーは
#ref(emag01.jpg)
で与えられる。
計算機シミュレーションコードでこの磁場が
3次元配列 Bx(nx,ny,nz), By(nx,ny,nz), Bz(nx,ny,nz)
で書かれているとき、
空間の体積が1に規格化されているとして、エネルギーは
#ref(emag02.jpg)
と書ける。この量を計算して出力するFortran90/95コードは、
print *,' energy = ', sum(Bx**2+By**2+Bz**2)/2
とわずか1行(定義式そのもの)を書けばよい。
配列のサイズ(nx,ny,nz)が実行時まで不定でもよい。
* ソースコードの書き方 [#z2e8de0f]
** 大文字・小文字 [#wf2dc4f4]
- Fortran90/95のソースコードではアルファベットの大文字と小文字は区別しない。
-- kobe と Kobe と KOBE は同じ
-- 文字列変数の中では当然区別される。
** 自由形式。C言語とほぼ一緒。 [#h0761bc0]
- 1行は132文字まで
* 予約語 [#sd6e6f11]
Fortran90/95には予約語が存在しない。
ユーザ定義の変数名であればコンパイラが賢く判断してくれる。
一方、C言語には32個も予約語がある。
C99ではさらに5個増えた。
たとえば・・・
次のCプログラムはコンパイルエラーになる。
main()
{
int dee, daa, doo;
int de, da, do;
dee = de*de;
}
* &color(#0000ff){【演習】}; [#g016d470]
下のプログラムをreserved_words.f95というファイルに保存し、
予約語がないことを確認せよ。
program reserved_words
implicit none
integer :: id=2, ie=3
integer :: if
if ( id==ie ) if=0
end program reserved_words
* コメント行の書き方 [#s3a2651c]
C言語では
/* この間がコメント */
である。(C99では // から行末までもコメントとなった。)
Fortran90/95では
! 一行の中でこの文字以降がコメント( C99やC++、JAVAの//と同じ)
* &color(#0000ff){【演習】}; [#nb9c04e8]
これまでに作ったプログラムに自由にコメントを入れよ。
* 定数 [#o5224599]
C言語ではプリプロセッサを使って
#define NX 100
とするが、
Fortran90/95ではC++のconstと同様
integer, parameter :: NX = 100
と書ける。コンパイラが型チェックをしてくれるのでバグが入りにくい。
* &color(#0000ff){【演習】}; [#m106df77]
上で書いたseries_one_forth.f95の定数ntermsを設定(integer, parameter nterms=100)した後に、
変更(nterms=200など)するとエラーになることを確認せよ。
* 数値演算 [#jbe87212]
四則演算はC言語と同じ
a+b, a-b, a*b, a/b
優先順位も同じ。
Fortran90/95でのべき乗は
a**b
余りは(C言語ではa % b)はFortran90/95では、
mod(a,b)
* &color(#0000ff){【演習】}; [#j448be26]
以下のプログラムをelemental_operators.f95に保存して、
コンパイル&実行せよ。
program elemental_operators
implicit none
real, parameter :: pi = 3.141593
complex :: z
print *, ' pi = ', pi
print *, ' pi+pi = ', pi+pi
print *, ' pi-pi = ', pi-pi
print *, ' pi*pi = ', pi*pi
print *, ' pi/pi = ', pi/pi
print *, 'pi**pi = ', pi**pi
z = (pi,pi)
print *,' z = ', z
print *,' z*pi = ', z**pi
print *,' z**z = ', z**z
end program elemental_operators
* 倍精度浮動小数点数の指定方法 [#idbc2893]
** kindパラメータ [#xe86ccb6]
計算科学では実数の物理量を数値的に表現するのに単精度ではなく、
倍精度の浮動小数点数を使うのが普通である。
(単精度実数では精度が不十分なため。)
まは、スーパーコンピュータは単精度浮動小数点ではなく、
倍精度浮動小数点数の演算(四則演算)を高速に処理できるよう設計されている。
従ってFortran90/95言語での倍精度浮動小数点数の表現方法に早めに慣れておくことは重要である。
Fortran90/95で倍精度浮動小数点数を表す方法は幾つかある。
最も簡単でportabilityの高い方法は以下の方法であろう。
まず、単精度浮動小数点数の精度に関係したある整数定数を定義する。
ここではその整数をSPという名前にする。
SPとはSingle Precision(単精度)の頭文字をとったものである。
integer, parameter :: SP = kind(1.0)
次に、このSPを使って単精度として指定した浮動小数点数の2倍の精度を持つ浮動小数点数、
つまり倍精度浮動小数点数に関係したある整数定数(ここではDPという名前)を以下のようにして定義する。
integer, parameter :: DP = selected_real_kind(2*precision(1.0_SP))
この整数定数DPはDouble Precisionを意味する。
こうして定義した二つの整数SPとDPを使って単精度浮動小数点や倍精度浮動小数点数を宣言する。
real(kind=SP) :: a
real(kind=DP) :: b
とすると、aは単精度浮動小数点、bは倍精度浮動小数点数である。
上のプログラムのkind=の部分は省略可能である。
つまり
real(SP) :: a
real(DP) :: b
としてもよい。
数値データの精度指定にもこのSPやDPを使う。
a = 1.0_SP ! 単精度の1.0
b = 1.0_DP ! 倍精度の1.0
サンプルプログラムを見てみよう。
program double_precision_real
implicit none
integer, parameter :: SP = kind(1.0)
integer, parameter :: DP = selected_real_kind(2*precision(1.0_SP))
real(SP) :: a
real(DP) :: b
a = 3.1415926535897323846264338327950288_SP
b = 3.1415926535897323846264338327950288_DP
print *,' a, b = ', a, b
end program double_precision_real
* &color(#0000ff){【演習】}; [#k81f0910]
上のプログラムをdouble_precision_real.f95という名前のファイルに保存し、
コンパイル&実行せよ。
* &color(#0000ff){【演習】}; [#oc3fe2e8]
double_precision_real.f95に現れる二つのkindパラメータSPとDPには実際にはどんな値が入っているか調べよ。
* 数字表現 [#b1205713]
- 浮動小数点数
-- 1. ! デフォルト精度
-- 0.1_SP ! kindパラメタSPの浮動小数点数
-- 3.141593_DP ! kindパラメタDPの浮動小数点数
-- 0.31415926535897932e-1_DP ! 上に同じ
* 型名 [#wba67087]
| 型 | C ( C99 ) | Fortran90/95 | 補註 |
| 文字 | char | character | |
| 文字列 | char[n+1] | character(len=n) | nは文字長|
| 整数 | int | integer | integer(kind=4)でも可 |
| 実数 | float | real | real(kind=SP)でも可 |
| 倍精度実数 | double | real(kind=DP) | |
| 「長い」整数 | long | integer(kind=8でも可) | kindの整数はシステム依存 |
| bool | ( _Bool ) | logical | 値は .true. または .false. |
| 複素数 | ( _Complex ) | complex | |
| 構造体 | struct | type | 詳しくは後述 |
* 構造体 (derived type) [#t9466bc0]
C言語の構造体とほとんど同じである。下の例をみよ。
program type
implicit none
type student
character(len=20) :: first_name, last_name
integer :: age
end type student
type(student) :: st
st = student("Albert", "Einsten", 19)
print *,st%first_name, st%last_name, st%age
end program type
構造体のメンバにアクセスするのに%を使う。
* &color(#0000ff){【演習】}; [#ga6836de]
上のプログラムをtype.f90に保存し、コンパイル&実行せよ。
* &color(#0000ff){【演習】}; [#k7ce894d]
student型の構造体変数をもう一つ(例えばst2という名前)を作り、
stのデータをst2にコピーせよ。
* 宣言 [#v139215c]
C言語では変数の宣言はブロックの先頭にまとめて置く。(C99やC++はもっと自由だが。)
Fortran90/95でも同じ。
* 暗黙の型宣言 [#u7784174]
Fortran90/95ではimplicit noneを省略すると「暗黙の型宣言」をしたことになる。
暗黙の型宣言とは、次の6つのアルファベット i,j,k,l,m,n で始まる変数は整数である等のルールである。
バグが入りやすいので、暗黙の型宣言は使わない方が良い。
Fortran90/95プログラムでは&color(#ff0000){常にimplicit none宣言をすること。};
* 暗黙の型宣言はなぜバグが入りやすいか [#y20a3e60]
宣言したつもりのない変数を間違って使っていても気がつかない可能性がある。
例えば、次のプログラムにはバグがある。すぐに見つけられるか?
program use_implicit_none
integer :: fresh_meat
flesh_meat = 100 ! yen
print *, "today's price = ", fresh_meat
end program use_implicit_none
* &color(#0000ff){【演習】}; [#qfdfd0e9]
上のプログラムをuse_implicit_nene.f95というファイルに書き込み、コンパイル&実行せよ。
- バグがあるのにコンパイラは教えてくれないだけでなく、
何事もないかのように正常終了する。しかも、それらしい答えを返す。
バグがあることに気がつかない!
* &color(#0000ff){【演習】}; [#jd16a9cf]
今作ったuse_implicit_nene.f95の2行目(program ...の次の行)に
implicit noneと書いてから、コンパイルせよ。
* 行の継続 [#id1f7906]
行末に&をつけると継続行
a = b + c + &
d + f
* セミコロン [#m11c8685]
セミコロンは改行と同じ
tmp = right
right = left
left = tmp
と
tmp = right; right = left; left = tmp
は同じ。
* 1次元配列 [#g365dcdc]
C言語では
array01[NX];
が大きさNXの1次元配列である。
メモリ上に、array01[0], array01[1], array01[2], ..., array01[NX-1]と並んでいる。
Fortran90/95では
integer, dimension(NX) :: array01
と宣言するとメモリ上に、array01(1), array01(2), array01(3), ..., array01(NX)と並ぶ。
&color(#ff0000){配列のインデックスは1から始まる。};
* &color(#0000ff){【演習】}; [#c5157375]
大きさNXの1次元整数配列array_01を作り、
各要素に1,2,3,...,NXを代入した上で、
全要素の和をとるFortran90/95プログラムを書け。
* 2次元配列 [#tfd40633]
厳密に言えばC言語では2次元配列はない。
あるのは「配列の配列」である。
つまり二つの整数インデックスiとjを使って
array02[j][i]
という形でアクセスできるものである。
このarray02を2次元配列とは呼べないということは、
array02のサイズ(インデックスiとjの範囲)は実行時に不定として、
array02をまるごと引数で受け取る関数がC言語では作れないことを考えれば納得出来るであろう。
* メモリ空間中での2次元配列の各要素の位置 [#z70388f1]
上記のarray02は、
array02[0][0], array02[0][1], array02[0][2], ..., array02[0][NX-1]
と並び、そして
(それに続く場合もあるし、
まったく別の場所にあるかもしれない)ある位置から、
array02[1][1], array02[1][1], array02[1][2], ..., array02[1][NX-1]
という順番で並ぶ。
一方、
Fortran90/95ではarray02(i,j)という形でアクセスできる量は本物の2次元配列である。
サイズがわかっている場合には
integer, dimension(NX,NY) :: array02
と宣言する。
不明の場合には、
integer, dimension(:,:) :: array02
と宣言し、後ほど(実行時に)メモリをallocateする。
メモリ上では、array02の要素は
array02(1,1), array02(2,1), array02(3,1), ..., array02(NX,1), array02(1,2),...
に全ての要素(NX*NY個)が連続して並ぶ。
配列の要素がメモリ空間中に連続してあることは、
メモリへのアクセス速度が極めて重要となるスーパーコンピューティングでは極めて重要である。
* 3次元配列 [#p1fdc2f1]
C言語では
array03[k][j][i]
が上の意味での「擬似」3次元配列である。
Fortran90/95では
array03[i][j][k]
が3次元配列で、
宣言は
integer, dimension(NX,NY,NZ) :: array03
または
integer, dimension(:,:,:) :: array03
とする。
* if構文 [#cd006621]
if文はC言語と似ている。下の例を見れば一目瞭然であろう。
if (criterion) then
action
end if
'''action'''が1行で書けるなら
if ( criteron ) action
ともできる。
else-if構文も簡単である。
if (criterion_1) then
action_1
else if (criterion_2 ) then
action_2
else if (criterion_3 ) then
action_3
end if
* 論理式 [#ja858b13]
これも下の例を見ればわかるであろう。
if (a==b) then ! aとbが等しい
if (a>b) then
if (a>=b) then
if (a<b) then
if (a<=b) then
if (a/=b) then ! aとbが等しくない
* 論理値 [#zab52f42]
bool変数はlogicalという型名を持ち、
.true.
か
.false.
の二つをとる。
logical :: a, b, c
a = .true.
b = .false.
c = a .and. b
c = a .or. b
* case 構文 [#n8034f08]
C言語でいうswitch文である。
select case (case expression)
case (case selector 1)
action 1
case (case selector 2)
action 2
case (case selector 3)
action 3
.
.
case default ! なくても良い。
default action
end select
C言語では普通breakが必要だがFortran90/95のcase文には不要である。
* &color(#0000ff){【演習】}; [#q4b290c4]
次のプログラムをcase.f95に保存し、実行せよ。
program case
implicit none
integer :: month
month = 6
select case (month)
case (1)
print *,'January.'
case (2)
print *,'February'
case (3:5)
print *,'Spring'
case default
print *,'Other than spring.'
end select
end program case
* &color(#0000ff){【演習】}; [#ae7fbd17]
後述するが、Fortran90/95は文字列の取り扱いがC言語よりもずっと簡単である。
例えば、長さの違う文字列をselect case構文で簡単に場合分けできる。
program case_string
implicit none
character(len=*), parameter :: color = 'white'
select case (color)
case ('aka')
print *, 'red'
case ('ao')
print *, 'blue'
case ('midori')
print *, 'green'
case default
print *, "I don't know the color."
end select
end program case_string
* 繰り返し構文(doループ) [#a5247d11]
C言語でいうfor文である。
** incrementが1の場合のdo-loop [#b3141e58]
program do_loop
implicit none
integer :: i
do i = 1 , 100
print *, ' i = ', i
end do
end program do_loop
** incrementが1以外の場合のdo-loop [#a3fc80d6]
program do_loop
implicit none
integer :: i
do i = 1 , 100 , 2
print *, ' i = ', i
end do
end program do_loop
* &color(#0000ff){【演習】}; [#pd6ce920]
- 上のプログラムをdo_loop.f95に保存し、increment値(上の場合2)を変えて実行せよ。
- do_loop.f95のdo文を
do i = 100 , -100 , -2
として実行せよ。
* exitとcycle [#m01ac20c]
整数のカウンターのないdo-loopも可能である。
do
...
end do
このままだと無限ループになるので、通常はある条件を満たせばループから抜け出すようにする。
C言語だとbreak文でループから抜け出す。(正確には最も内側のループから抜け出す。)
Fortran90/95でbreakに相当するのはexitである。
do
...
if (condition) exit !---+
end do ! | ループから抜け出す。
!<--+
C言語ではループの先頭に戻る時にはcontinue文を使うが、
それに相当するのはFortran90/95ではcycleである。
do !<--+
... ! | 戻る
if (condition) cycle !---+
...
end do
* &color(#0000ff){【演習】}; [#x7b8bfe7]
if文、select case文、do文を使って自由にプログラムを作ってみよ。
* 関数 [#rdd8ed51]
C言語と同様に関数(function)という手続き(procedure)は、
Fortran90/95プログラムの重要な構成部品である。
関数の使い方は以下の例を見ればわかるであろう。
* &color(#0000ff){【演習】}; [#tbabddbe]
- 次のプログラムをfunction01.f95として保存し、実行せよ。
integer function next_int(i)
implicit none
integer, intent(in) :: i
next_int = i+1
end function next_int
program function01
implicit none
integer, external :: next_int
print *, 'ans = ', next_int(-100)
end program function01
intent(in)は引数iが入力引数であり、関数next_intの内部で変更されることがないことを保証するものである。
- 関数を以下のように内部副プログラムとしてメインの中に含むこともできる。
次のプログラムをfunction02.f95として保存し、実行せよ。
//(内部副プログラムについては後述。)
program function02
implicit none
print *, 'ans = ', next_int(-100)
contains
integer function next_int(i)
integer, intent(in) :: i
next_int = i+1
end function next_int
end program function02
* サブルーチン [#ja86e233]
関数の本来の機能は入力に応じて出力を返すものであるが、
出力(返り値)が不要な場合も多い。
C言語ではvoid関数とするこのような関数を
Fortran90/95ではsubroutineと呼ぶ。
サブルーチンを呼び出すにはcall '''subroutine名'''とする。
以下の例をみよ。
subroutine next_int(input, output)
implicit none
integer, intent(in) :: input
integer, intent(out) :: output
output = input + 1
end subroutine next_int
program subroutine01
implicit none
integer :: i, j
i = 10
call next_int(i,j)
print *, 'ans = ', j
end program subroutine01
* 値渡しと参照渡し [#t6be9c88]
C言語は値渡し、Fortran90/95は参照渡しである。
C言語が値渡しであることは、以下の例でkの値が変わっていないことから確認できる。
void pass(int i)
{
i = 0;
printf("i=%d\n", i);
}
main()
{
int k = 2010;
printf("before: k = %d\n", k);
pass(k);
printf("after: k = %d\n", k);
}
これと'''似た'''プログラム(同じではない)をFortran90/95で書くと以下のようになる。
subroutine pass(i)
implicit none
integer :: i
i = 0;
print *, "i=", i
end subroutine pass
program call_by_reference
implicit none
integer :: k = 2010
print *, "before: k = ", k
call pass(k)
print *, "after: k = ", k
end program call_by_reference
このプログラムではkの値はサブルーチンpassを読んだ後に変更されている。
* &color(#0000ff){【演習】}; [#p11ef34e]
上の二つのプログラムをそれぞれcall_by_value.c、call_by_reference.f95という名前で保存し、実行せよ。
* 入出力属性 [#q286f64c]
値渡しに比べて参照渡しは値をコピーする必要がないので速い。
従って計算速度を重視するFortran90/95言語では値渡しを採用していないのは当然である。
だが、上のcall_by_reference.f95プログラムで見たように、
引数として渡した変数がそのサブルーチン内部で勝手に(誤って)変更されると見つけにくいバグになり、危険である。
このような事故を防ぐためにFortran90/95には引数に入出力属性をつけることができる。
サブルーチンincrementの引数iは入力属性をもつと指定すると(下のプログラムの2行目)、
コンパイラがエラーを出してくれる。
subroutine increment(i) ! コンパイルエラーを出してくれる
integer, intent(in) :: i
i = 0;
print *, "i=", i
end subroutine increment
program call_by_reference
implicit none
integer :: k = 2010
print *, "before: k = ", k
call increment(k)
print *, "after: k = ", k
end program call_by_reference
* &color(#0000ff){【演習】}; [#haba2285]
call_by_reference.f95を上のように変更してコンパイルせよ。
* 入出力属性の種類 [#h514b99e]
入出力属性には以下の3種類がある。
| intent(in) | 入力 | その手続き内で値は変更されない変数 |
| intent(out) | 出力 | その手続き内で値が設定される変数 |
| intent(inout) | 入出力 | 両者の混合。デフォルト。 |
バグの混入を防ぐために、Fortran90/95プログラムでは&color(#ff0000){すべての引数に入出力属性をつける};ことを強く勧める。
------------------------------
as of &_now; (&counter;)