我是CUDA编程的新手。我有一个CUDA子程序,希望用简单的来源对扩散方程进行建模:
attributes(global) subroutine diff_time_stepper(v,diffconst)
real*8 :: v(:,:)
real*8 :: diffconst
real*8 :: vintermed
integer :: i,j,m
integer :: nx, ny
nx=256
ny=256
i=(blockIdx%x-1)*blockDim%x+threadIdx%x
j=(blockIdx%y-1)*blockDim%y+threadIdx%y
if (i<nx .and. j<ny .and. i>1 .and. j>1) then
vintermed=v(i,j)+diffconst*(v(i-1,j)-2.*v(i,j)+v(i+1,j)+v(i,j-1)-2.*v(i,j)+v(i,j+1))
v(i,j)=vintermed
! add a source for the heck of it
if (i==64 .and. j==64) v(i,j)=v(i,j)+1
endif
end subroutine
我的问题:这个例程似乎有效,给出了合理的结果(尽管没有像我希望的那样快速运行)。但我在这里有“后向依赖”吗?特别是,vinterm由一个涉及几个v的函数设置,然后v被设置为等于vtinerm。然后在此计算后设置v(64,64)。这些潜在的问题吗?更一般地说,尽管我已经找到了很多关于如何为CUDA编程的讨论,但我发现很少有关于这种后向依赖问题的讨论,这在我看来是最重要的。谁能指点我对此进行一次很好的讨论?感谢。
答案 0 :(得分:2)
是的,你这样做。一般来说,各种线程正在读入和更新v,这是一个不可预测的顺序(尽管有关于同一块上的warp中线程行为的事情,等等)。这是真的(有不同的警告)比如OpenMP或者你最喜欢的基于CPU的线程框架。
扩散问题比大多数更加稳健 - 例如,高斯西得乐或雅可比迭代在读取之前明确地使用更新值 - 但是由于不同的线程具有不同的值,您仍然会遇到问题时间步长;特别是在更新之前读入或不读取v(64,64)的线程通常会导致源周围峰值的形状不一致。
通过在阅读后同步线程,确保在块中不会发生这种情况:
...
real*8 :: left, right, up, down, centre
nx=256
ny=256
i=(blockIdx%x-1)*blockDim%x+threadIdx%x
j=(blockIdx%y-1)*blockDim%y+threadIdx%y
if (i<nx .and. j<ny .and. i>1 .and. j>1) then
left = v(i-1,j)
right = v(i+1,j)
up = v(i, j+1)
down = v(i, j-1)
centre= v(i,j)
endif
call syncthreads()
if (i<nx .and. j<ny .and. i>1 .and. j>1) then
v(i,j) = centre + diffconst*(left+right+up+down-4.*centre)
if (i==64 .and. j==64) v(i,j)=v(i,j)+1
endif
但这只会将问题推到块边界而不是线程边界;您仍然不知道块正在读取/更新v
的顺序。但同步块的唯一方法是使用内核的末尾。
有几种解决方法,都涉及使用更多内存或更少并行性。一种方法是让所有人都阅读vold,比如说,并更新vnew;然后每个人都只是从旧数组中读取并更新新数据,然后就没有同步问题了。然后你只需要改变每个时间步长的新旧含义,这样每个奇数时间步读入vold并输出vnew,而每个偶数时间步都是相反的。