异步OpenACC内核中数组访问冲突如何优雅解决?
解决方案
问题根源
你遇到的问题本质是异步队列间的读写数据竞争:OpenACC运行时会将带async属性的任务提交到独立队列异步执行,没有显式声明任务依赖的情况下,任务B的kernel会在任务A的kernel还没读完原始array的时候就启动修改数组元素,导致均值计算读到了部分翻倍后的值,结果异常。CPU端运行正常是因为多数CPU侧的OpenACC实现默认会将异步任务串行调度,没有真的并发执行,刚好避开了竞争。
另外你的代码还存在语法问题:有!$acc end data但没有对应的!$acc data起始声明,运行时可能出现未定义行为。
方案1:使用独立只读副本(最优,性能最高)
这个方案最适配你的场景,两个任务本身逻辑独立,直接给任务A提供一份原始数组的只读副本,完全不需要任务间依赖,两个kernel可以真正并行执行,没有任何冲突。你代码中已经定义了未使用的array_d,可以直接复用:
program test implicit none integer, parameter :: n = 1000000 real(8) :: mean real(8) :: array(n) real(8) :: array_d(n) ! 初始化数组,同时给任务A准备只读副本 array = [(i, i=1, n)] array_d = array !$acc data copyin(array_d, array) copyout(mean, array) ! 任务A读副本array_d,和任务B无冲突 !$acc kernels async num_gangs(1) mean = 0d0 !$acc loop independent reduction(+:mean) do i = 1, n mean = mean + array_d(i) end do mean = mean / n !$acc end kernels ! 任务B改原array,不需要等任务A !$acc kernels async !$acc loop independent do i = 1, n array(i) = array(i) * 2 end do !$acc end kernels ! 等待两个异步队列都完成 !$acc wait !$acc end data print "(10(g0.2, x))", array(:10) print "('mean = ', g0.2)", mean end program
优缺点
- 优点:无任务依赖,两个kernel可以完全并行,性能最高,逻辑简单不容易出依赖错误
- 缺点:需要额外存储一份数组,显存紧张的超大规模场景下不适用
方案2:显式声明数据依赖(省内存)
如果不想额外占用显存,可以用OpenACC的depend子句显式声明数据依赖,让运行时自动保证任务A读完数组之后,任务B再修改数组:
program test implicit none integer, parameter :: n = 1000000 real(8) :: mean real(8) :: array(n) array = [(i, i=1, n)] !$acc data copyin(array) copyout(mean, array) ! 声明任务A对array是只读依赖 !$acc kernels async num_gangs(1) depend(readonly:array) mean = 0d0 !$acc loop independent reduction(+:mean) do i = 1, n mean = mean + array(i) end do mean = mean / n !$acc end kernels ! 声明任务B对array是读写依赖,运行时会自动等所有readonly依赖的任务完成再启动 !$acc kernels async depend(readwrite:array) !$acc loop independent do i = 1, n array(i) = array(i) * 2 end do !$acc end kernels !$acc wait !$acc end data print "(10(g0.2, x))", array(:10) print "('mean = ', g0.2)", mean end program
优缺点
- 优点:不需要额外占用显存
- 缺点:任务B必须等任务A读取完全部数组才能启动,并发度低于方案1
内容的提问来源于stack exchange,提问作者Neraste
相关产品推荐
相关产品推荐

