Fortran+OpenACCのsubroutine内可変長配列
消えたCUDA関連の旧ブログ記事を復元するひとり Advent Calendar 2024の記事です。
なんの話か
Fortran/OpenACCのループ内サブルーチンで可変長配列を用いた場合、NVIDIA GPU上ではどこのメモリにどのタイミングで確保されるか調べてみました。
コンパイル時に配列長が決定していないためレジスタに作られることはないだろうとの予想のもと、カーネル内mallocを呼んでいるのかなーと言う気持ちで調べていきます。
実験コード
調査に用いたコードはこちらです。
inline展開されると困るので、2ファイルに分けて書いています。
▼sub.f90
subroutine sub_routine(a, array_size)
!$acc routine seq
real(8) :: a(:)
integer :: array_size
real(8), dimension(array_size) :: array
integer :: i
do i = 1, array_size
a(i) = array(i)
end do
end subroutine
▼main.f90
program main
interface
subroutine sub_routine(a, array_size)
!$acc routine seq
real(8) :: a(:)
integer :: array_size
end subroutine
end interface
integer :: array_size = 10000
integer :: n = 100000
real(8), dimension(:), allocatable :: a
allocate(a(n))
!$acc kernels copy(a(1:n)), copyin(array_size)
do i = 1, n
call sub_routine(a, array_size)
enddo
!$acc end kernels
end program
(Makefile等はこちら enp1s0/openacc-subroutine - GitHub)
2ファイル用意したものの、実際にはsub.f90があれば調査できます。
コンパイルしたsub.o内に含まれるPTXをcuobjdumpで覗きます。
!...
$L__tmp0:
.loc 1 1 1
ld.u64 %rd2, [%rd22+56];
ld.u64 %rd3, [%rd22+80];
add.s64 %rd24, %rd2, %rd3;
add.s64 %rd44, %rd24, -1;
ld.u32 %r1, [%rd21];
mul.wide.s32 %rd25, %r1, 8;
{
.reg .b32 temp_param_reg;
.param .b64 param0;
st.param.b64 [param0+0], %rd25;
.param .b64 retval0;
call.uni (retval0),
malloc,
(
param0
);
ld.param.b64 %rd5, [retval0+0];
}
setp.ne.s64 %p1, %rd5, 0;
@%p1 bra $L__BB0_4;
!...
予想通りmallocが呼ばれていることが確認できます。
つまり可変長配列の実体はDeviceメモリ上で、カーネル実行の最中に確保されるようです。
では、可変長配列をサイズ決め打ちの固定長にするとどうなるかと言うと、
- サイズが小さいときはレジスタ
- サイズが多いときは同様にカーネル内malloc
となることが確認できます。
おわり
カーネル内mallocというものを知った時誰が使うのだろうと思っていましたが、OpenACC/Fortranの実装で使われていたのですね。