コンパイラはifort.
単語帳.毎回検索するのが面倒なので転載多め.元URLあり.
自前の検証メモも.
配列の形状
大きさ0に割り付けられる
program main
implicit none
integer, allocatable :: a(:)
allocate(a(0)); a(:) = 1 ! エラーは発生しない
write(*, *) a ! 何も表示されない
write(*, *) allocated(a) ! T
write(*, *) a(0) ! forrtl: severe (174): SIGSEGV, segmentation fault occurred
end program main
大きさ0の配列・未割り付けの配列のsize
は0.
program main
implicit none
integer, allocatable :: a(:)
allocate(a(3)); a(:) = 1
write(*, *) size(a) ! 3
deallocate(a)
allocate(a(0)); a(:) = 1
write(*, *) size(a) ! 0
deallocate(a)
write(*, *) size(a) ! 0
end program main
これらの性質を用いると,allocatableな配列を返すサブルーチン+そのsize
で繰り返し処理,という場面で便利.
手続きに渡される配列の添え字の下限値は引き継がれない
p.90
サブルーチンや関数で引数の配列を(:)
と指定した場合,
元々の添え字の下限値 (lbound
)は引き継がれず,1が仮定される.
添え字の下限値も渡したい場合は,
下限値は引数として渡す + 上限値はそれとsize
から計算
反対に,手続きの中で割り付けられた配列の上限下限は引き継がれる
module mymod
implicit none
contains
subroutine mysub(arr)
integer, intent(out), allocatable :: arr(:,:)
allocate(arr(-3:2,-1:4), source=0)
end subroutine mysub
end module mymod
program main
use mymod, only: mysub
implicit none
integer, allocatable :: arr(:,:)
call mysub(arr)
write(*, *) lbound(arr, 1), ubound(arr, 1) ! -3 2
write(*, *) lbound(arr, 2), ubound(arr, 2) ! -1 4
end program main
配列間の代入
2つの配列の間で次元の上限・下限が異なっていても(:)なら値を渡せる
program main
implicit none
integer a(1:3), b(4:6)
integer i
a(:) = 1
b(:) = 4
b(:) = a(:)
write(*, *) a ! 4 4 4
b(:) = 4
do i = 1, 3
b(i) = a(i)
enddo
write(*, *) b ! 1 1 1
end program main
大きさの異なる配列間の代入
小配列に大配列を代入 (小配列 = 大配列): 大配列の値がで上書き
大配列に小配列を代入 (大配列 = 小配列): 大配列の残りの要素は保持されない場合がある
以下の例では
-
大配列(:) = 小配列(:)
: 残りの要素は0に -
大配列 = 小配列
: 残りの要素は保持 (5)
という結果だったが,特に前者の結果はこれらを実行する順番でも変化した
(例: b = a
, init_array
, b(:) = a(:)
の順に実行すると値は保持された).
明示的に値を保持するためには,小さい方のサイズに合わせるなどした方が無難?
program main
implicit none
integer, allocatable :: a(:), b(:)
integer :: asize, bsize, n
asize = 2
bsize = 3
call init_array
! a 1 2
! b 3 4 5
call print_array
n = min(size(a), size(b))
b(:) = a(:) ! b 1 2 0
! b = a ! b 1 2 5
! b(:n) = a(:n) ! b 1 2 5
! 小 = 大は同じ結果
! a(:) = b(:)
! a = b
! a(:n) = b(:n)
call print_array
! a 3 4
! b 3 4 5
contains
subroutine init_array
integer :: i
if (allocated(a)) deallocate(a)
if (allocated(b)) deallocate(b)
allocate(a(asize), source=[(i, i = 1, asize)])
allocate(b(bsize), source=[(i, i = asize+1, asize+bsize)])
end subroutine init_array
subroutine print_array
integer :: i
write(*, '(a,*(1x,i0))') 'a', (a(i), i = 1, asize)
write(*, '(a,*(1x,i0))') 'b', (b(i), i = 1, bsize)
end subroutine print_array
end program main
組み込み手続き
他の配列に形状・値を渡すallocate(source=)
allocate(b, source=a)
でaの形状・値をbにコピー
- このとき元の配列aは変更なし
move_alloc(from=a, to=b)
との違いで上記+aは解放
-
allocate(b, source=a); deallocate(a)
と同じ
大きさ0の配列・未割り付けの配列にも使える
program main
implicit none
integer, allocatable :: a(:), b(:)
allocate(a(0)); a(:) = 0
allocate(b, source=a)
write(*, *) allocated(b), size(b) ! T 0
deallocate(a, b)
allocate(b, source=a)
write(*, *) allocated(b), size(b) ! F 0
end program main
構造体にも使える
program main
implicit none
type mycls1
integer x, y
character(len=8) t
end type mycls1
type mycls2
integer, allocatable :: x(:), y(:)
character(len=8) t
end type mycls2
type(mycls1), allocatable :: a(:), b(:)
type(mycls2), allocatable :: c(:), d(:)
type(mycls1) objmycls1
type(mycls2) objmycls2
integer i, n
objmycls1%x = 1
objmycls1%y = 2
objmycls1%t = 'test'
n = 3
allocate(a(n)); a(:) = objmycls1
allocate(b, source=a)
do i = 1, n
write(*, *) b(i)%x, b(i)%y, b(i)%t
enddo
allocate(objmycls2%x(2)); objmycls2%x(:) = 1
allocate(objmycls2%y(2)); objmycls2%y(:) = 2
objmycls2%t = 'test'
allocate(c(n)); c(:) = objmycls2 ! x(:)とy(:)は勝手に割り付けられる
do i = 1, n
write(*, *) c(i)%x, c(i)%y, c(i)%t
enddo
allocate(d, source=c) ! x(:)とy(:)は勝手に割り付けられる
do i = 1, n
write(*, *) d(i)%x, d(i)%y, d(i)%t
enddo
end program main
構造体の配列の解放
allocate(b, source=a)
とmove_alloc(from=a, to=b)
の違い
上記を実行すると,
-
b
の配列の大きさ・値はどちらも同じだが, -
b
のあるメモリアドレス (メモリ番地) が異なる-
allocate
:b
はa
と異なるアドレスに-
a
も残っているので当たり前
-
-
move_alloc
:b
はa
と同じアドレスに- 同時に
a
はdeallocate
されるので,そこを自動で再利用してくれると思えば楽
- 同時に
-
move_alloc
を使うと配列の延長をする際のメモリコピーの回数が減る
= 実行速度が上がる!!
nag: 3.5 MOVE_ALLOC組込みサブルーチン
この情報は@Authnsさんから教えて頂きました.ありがとうございました!
program main
implicit none
integer, allocatable :: a(:), b(:)
integer :: i, n
n = 3
allocate(a(n), source=[(i, i = 1, n)])
call print_array(a(:))
! 1 2 3
! 7364736 7364740 7364744
write(*, '(a)') 'allocate:'
allocate(b, source=a)
call print_array(b(:))
deallocate(b)
! 1 2 3
! 7377472 7377476 7377480
write(*, '(a)') 'move_alloc:'
call move_alloc(from=a, to=b)
call print_array(b(:))
deallocate(b)
! 1 2 3
! 7364736 7364740 7364744
contains
subroutine print_array(arr)
integer, intent(in) :: arr(:)
integer :: i, n
n = size(arr)
write(*, '(*(i0,1x))') (arr(i), i = 1, n)
write(*, '(*(i0,1x))') (loc(arr(i)), i = 1, n)
end subroutine print_array
end program main
move_alloc
の方が圧倒的に速い
以下のコードで実行時間を比較.
これが果たして同じコードなのかには自信がない
(1回のiterationの中でallocate, deallocateを2回ずつやっているつもり, これを1e5回)
配列の長さn
|
allocate [sec] | move_alloc [sec] |
---|---|---|
1e4 | 0.205400000000000 | 5.000000000000000E-004 |
1e5 | 2.50440000000000 | 5.000000000000000E-004 |
1e6 | 52.5643000000000 | 4.000000000000000E-004 |
2次元版 (n×n)
配列の大きさn
|
allocate [sec] | move_alloc [sec] |
---|---|---|
1e2 | 0.232900000000000 | 7.000000000000000E-004 |
1e3 | 61.9367000000000 | 5.999999999999999E-004 |
program main
implicit none
integer, allocatable :: a(:), b(:)
integer :: i, n, itr, itr_max
real(8) :: time
integer :: t1, t2, t_rate, t_max, diff
n = 1e6
itr_max = 1e5
allocate(a(n), source=[(i, i = 1, n)])
write(*, '(a)') 'allocate:'
call clock_start
do itr = 1, itr_max
allocate(b, source=a)
deallocate(b)
allocate(b, source=a)
deallocate(b)
enddo
call clock_stop
write(*, '(a)') 'move_alloc:'
call clock_start
do itr = 1, itr_max
call move_alloc(from=a, to=b)
call move_alloc(from=b, to=a)
enddo
call clock_stop
contains
subroutine clock_start
call system_clock(t1)
end subroutine clock_start
subroutine clock_stop
call system_clock(t2, t_rate, t_max)
if (t2 < t1) then
diff = (t_max - t1) + t2 + 1
else
diff = t2 - t1
endif
write(*, *) diff / dble(t_rate)
end subroutine clock_stop
end program main
その他文法
引数によって返す配列の大きさを変える関数
書き方は以下の通り.返り値にallocatable属性を付与するだけでOK.
function myfunc(nlen) result(arr)
integer, intent(in) :: nlen
integer, allocatable :: arr(:)
! if (allocated(arr)) deallocate(arr) ! 不要 (あってもエラーにはならない)
allocate(arr(nlen)); arr(:) = 1
end function myfunc
1次元の配列を引数の大きさに割り付け+全てに1を代入という関数.
これを呼び出す側では,
- 返ってくる配列の大きさに予め割り付けなくてもOK
- 違う大きさに割り付けられていても解放する必要なし
- 仮に違う大きさに割り付けられていた場合,関数の返り値が優先(上書きされる)
- 呼び出す側は
(:)
をつけない- 期待通りに割り付けられない
- もし最初から割り付けられていた場合
- 大きさが違ってもエラーにはならない
- その一部の値を上書きする
- それ以外は(以下の例だと)0に,元の値は保持されない
-
(:)
は配列の形状は変えず,各要素の値だけを操作するという意味?
- 1次元・長さ1の配列でもintegerには結果を取得できない
shape
はこのような性質を持つ組み込み関数.
subroutine test_alloc
integer, allocatable :: arr(:)
arr = myfunc(1)
write(*, *) arr ! 1
arr = myfunc(2)
write(*, *) arr ! 1 1
write(*, *) allocated(arr), size(arr) ! F 0
arr = myfunc(1)
write(*, *) sum(arr) ! 1
write(*, *) allocated(arr), size(arr) ! T 1
! 違う大きさに割り付けられていても解放する必要なし
arr = myfunc(2)
write(*, *) sum(arr) ! 2
write(*, *) allocated(arr), size(arr) ! T 2
! 違う大きさに割り付けられていた場合,関数の返り値が優先
deallocate(arr)
allocate(arr(3)); arr(3) = 0
write(*, *) allocated(arr), size(arr) ! T 3
arr = myfunc(2)
! arr = myfunc(4)
write(*, *) sum(arr) ! 2 (4, myfuncの引数を4にした場合)
write(*, *) allocated(arr), size(arr) ! T 2 (4)
end subroutine test_alloc
! arr(:) = で呼び出す版
subroutine test_alloc
integer, allocatable :: arr(:)
arr(:) = myfunc(1)
write(*, *) arr ! 何も表示されない
write(*, *) allocated(arr), size(arr) ! F 0
arr(:) = myfunc(2)
write(*, *) arr ! 何も表示されない
write(*, *) allocated(arr), size(arr) ! F 0
allocate(arr(3)); arr(:) = 3
arr(:) = myfunc(1)
write(*, *) arr ! 1 0 0, 1 3 3ではない
write(*, *) allocated(arr), size(arr) ! T 3
end subroutine test_alloc
! 配列の代わりにスカラーで呼び出そうとした版 (コンパイルエラー)
subroutine test_alloc
integer :: scalar
scalar = myfunc(1) ! error #6366: The shapes of the array expressions do not conform
write(*, *) scalar
end subroutine test_alloc