3

我是 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 编程的讨论,但我发现关于后向依赖问题的讨论很少,这在我看来是最重要的。谁能指出我对此进行了很好的讨论?谢谢。

4

1 回答 1

2

是的你是。通常,各种线程正在以不可预测的顺序读取和更新 v(尽管您可以说一些关于同一块上的 warp 中线程的行为等的事情)这是正确的(有不同的警告)比如说 OpenMP 或任何你最喜欢的基于 CPU 的线程框架。

扩散问题比大多数问题更稳健——例如,Gauss Sidel 或 Jacobi 迭代明确地在读取值之前使用更新值——但你仍然会遇到问题,因为不同的线程具有来自不同时间步的值;特别是在更新之前读入或不读入 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;然后每个人都只是从旧数组中读取并更新新数组,然后就没有同步问题了。然后你只需在每个时间步中切换 old 和 new 的含义,因此每个奇数时间步都会读取 vold 并输出 vnew,而每个偶数时间步都会反过来。

于 2012-05-22T16:12:34.053 回答