OpenACC操作派生类型成员时内核非法地址错误及子例程问题排查
测试代码
模块定义
module test implicit none ! type test_type integer :: n1,n2,n3 integer, pointer, dimension(:,:,:) :: dat end type test_type ! contains subroutine copydat(datain,dataout,indi,inde) !$ACC ROUTINE VECTOR implicit none integer, intent(in) :: indi, inde type(test_type), intent(in) :: datain type(test_type), intent(out) :: dataout ! dataout%dat(:,indi:inde,:) = datain%dat(:,indi:inde,:) end subroutine copydat ! subroutine get_indices(n2,i_chunk,n_chunks,indi,inde) !$ACC ROUTINE SEQ implicit none integer, intent(in) :: n2, i_chunk, n_chunks integer, intent(out) :: indi, inde ! indi = 1 + (i_chunk - 1)*n2/n_chunks inde = i_chunk*n2/n_chunks end subroutine get_indices end module test
主程序
program main use openacc use test implicit none integer :: n_chunks, i_chunk, n1, n2, n3, i1, i2, i3, indi, inde type(test_type) :: data1, data2 open(10,file='num.txt') read(10,*) n1 read(10,*) n2 read(10,*) n3 read(10,*) n_chunks close(10) write(*,'(4(A,I3),A)') 'Data of size ', n1 , ', ', n2, ', ', n3, ', splitted in ', n_chunks, ' chunks along dim2' data1%n1 = n1 data1%n2 = n2 data1%n3 = n3 data2%n1 = n1 data2%n2 = n2 data2%n3 = n3 allocate(data1%dat(n1,n2,n3),data2%dat(n1,n2,n3)) do i3 = 1,n3 do i2 = 1,n2 do i1 = 1,n1 data1%dat(i1,i2,i3) = i1 + (i2-1)*n1 + (i3-1)*n1*n2 end do end do end do !$ACC ENTER DATA COPYIN(data1,data2) !$ACC ENTER DATA CREATE(data1%dat,data2%dat) !$ACC UPDATE DEVICE(data1%dat) !$ACC PARALLEL DEFAULT(present) PRIVATE(indi,inde) !$ACC LOOP GANG INDEPENDENT PRIVATE(indi,inde) do i_chunk=1,n_chunks ! --- Current chunk indices --- call get_indices(n2,i_chunk,n_chunks,indi,inde) print *, "#", __pgi_gangidx(), ": ", indi, inde data2%dat(:,indi:inde,:) = data1%dat(:,indi:inde,:) !call copydat(data1,data2,indi,inde) end do !$ACC END LOOP !$ACC END PARALLEL !$ACC UPDATE SELF(data2%dat) !$ACC EXIT DATA DELETE(data1%dat,data2%dat) !$ACC EXIT DATA DELETE(data1,data2) print *, maxval(abs(data1%dat-data2%dat)) deallocate(data1%dat,data2%dat) end program main
测试环境
num.txt内容:128、256、32、64(每个gang处理128×4×32的数据块)- 硬件:Tesla P100
- 编译命令:
nvfortran -acc -ta=tesla:cc60 -fast -Minfo=accel -o main.exe test.F90
问题与解答
1. PRIVATE子句组合崩溃的原因
问题现象
四种PRIVATE子句组合中仅Try D崩溃(报错:call to cuStreamSynchronize returned error 700: Illegal address during kernel execution):
- Try A:
!$ACC PARALLEL DEFAULT(present)+!$ACC LOOP GANG INDEPENDENT(正常) - Try B:
!$ACC PARALLEL DEFAULT(present) PRIVATE(indi,inde)+!$ACC LOOP GANG INDEPENDENT PRIVATE(indi,inde)(正常) - Try C:
!$ACC PARALLEL DEFAULT(present) PRIVATE(indi,inde)+!$ACC LOOP GANG INDEPENDENT(正常) - Try D:
!$ACC PARALLEL DEFAULT(present)+!$ACC LOOP GANG INDEPENDENT PRIVATE(indi,inde)(崩溃)
解答
这是nvfortran对OpenACC私有变量作用域的处理逻辑导致的:
- Try A:无显式PRIVATE子句,
indi/inde默认是共享变量,但每个gang独立执行循环迭代时,编译器会隐式为每个gang创建私有副本,因此无冲突。 - Try B/C:在PARALLEL或LOOP层级显式声明PRIVATE,
indi/inde为gang层级私有变量,每个gang拥有独立副本,访问合法。 - Try D:仅在LOOP GANG层级声明PRIVATE,PARALLEL层级未声明。此时
indi/inde本质是PARALLEL区域的共享变量,内层强行PRIVATE会导致存储混淆,触发非法地址访问。
OpenACC标准中,LOOP子句的PRIVATE仅作用于循环迭代的gang,但变量存储生命周期由外层PARALLEL区域决定,外层未声明PRIVATE时,内层PRIVATE会引发存储冲突。
2. 调用copydat子例程的异常行为
问题现象
- 直接执行数组赋值:正常运行,vector_length=128。
- 调用
copydat:vector_length变为32且崩溃;n2改为128时不崩溃但vector_length仍为32。
解答
vector_length变化原因:
copydat被声明为!$ACC ROUTINE VECTOR,表示由向量线程执行,nvfortran会为向量例程默认分配较小的vector_length(如32);而直接数组赋值时,编译器会自动适配硬件设置vector_length(P100默认128)。崩溃原因:
n2=256时,每个gang处理的是非连续数组切片,copydat中的数组赋值由向量线程并行执行,但编译器未正确处理派生类型指针的设备地址映射,导致非法内存访问。n2=128时切片偏移量变小,映射错误未触发,但属于未定义行为。修复方案:将
copydat的!$ACC ROUTINE VECTOR改为!$ACC ROUTINE SEQ(该子例程为每个gang独立执行,无需向量并行);或在子例程内部添加显式OpenACC循环指令明确并行区域。
3. 调用deldat子例程释放内存崩溃
问题现象
将主程序显式deallocate替换为调用deldat子例程时崩溃,编译输出存在差异。
解答
这是nvfortran对派生类型+OpenACC的支持不足导致的,并非预期行为:
- 显式
deallocate时,编译器会自动关联OpenACC数据区域的释放逻辑(设备端数据已通过EXIT DATA DELETE清理,主机端释放合法)。 - 调用
deldat时,编译器未识别到该子例程释放的是已注册到OpenACC数据区域的指针,设备端映射信息未正确清理,导致主机端释放时地址错误。
修复方案:
- 在
deldat中添加OpenACC指令显式同步设备数据:subroutine deldat(data0) implicit none type(test_type), intent(inout) :: data0 !$ACC ENTER DATA PRESENT(data0) deallocate(data0%dat) !$ACC EXIT DATA DELETE(data0%dat) end subroutine deldat - 或确保在调用
deldat前,设备端数据已通过EXIT DATA完全释放。
内容的提问来源于stack exchange,提问作者Fabio Riva

