Matlab CUDA 基础实验

3

(已经正确并详细回答,请见下方)

我开始使用Matlab和GPU(Nvidia GTX660)进行实验。

现在,我编写了这个简单的蒙特卡罗算法来计算圆周率。下面是CPU版本的代码:

function pig = mc1vecnocuda(n)
countr=0;
A=rand(n,2);
 for i=1:n

   if norm(A(i,:))<1
    countr=countr+1;
   end
 end
pig=(countr/n)*4;
end

这在CPU上执行起来所需的时间非常短,它会将100,000个点“投”到单位圆中:
   >> tic; mc1vecnocuda(100000);toc;

      Elapsed time is 0.092473 seconds.

看看使用GPU优化的算法会发生什么:
   function pig = mc1veccuda(n)
   countr=0;
   gpucountr=gpuArray(countr);
   A=gpuArray.rand(n,2);
   parfor (i=1:n,1024)
    if norm(A(i,:))<1
        gpucountr=gpucountr+1;
    end
   end

   pig=(gpucountr/n)*4;
   end

现在,这个执行需要很长时间:
>> tic; mc1veccuda(100000);toc;
Elapsed time is 21.137954 seconds.

我不明白为什么。我使用了1024个工作线程的parfor循环,因为通过gpuDevice查询我的nvidia卡时,1024是gtx660上允许同时执行的最大线程数。

能有人帮助我吗?谢谢。

编辑:这是更新后避免使用IF的版本:

function pig = mc2veccuda(n)
countr=0;
gpucountr=gpuArray(countr);
A=gpuArray.rand(n,2);
parfor (i=1:n,1024)

    gpucountr = gpucountr+nnz(norm(A(i,:))<1);

end

pig=(gpucountr/n)*4;
end

这是按照Bichoy的指引编写的代码(可实现预期结果):

function pig = mc3veccuda(n)
countr=0;
gpucountr=gpuArray(countr);
A=gpuArray.rand(n,2);
Asq = A.^2;
Asqsum_big_column = Asq(:,1)+Asq(:,2);
Anorms=Asqsum_big_column.^(1/2);
gpucountr=gpucountr+nnz(Anorms<1);

pig=(gpucountr/n)*4;
end

请注意n=1000万时的执行时间:
>> tic; mc3veccuda(10000000); toc;
Elapsed time is 0.131348 seconds.
>> tic; mc1vecnocuda(10000000); toc;
Elapsed time is 8.108907 seconds.

我没有测试我的原始cuda版本(for/parfor),因为n=10000000时需要执行数小时。

太棒了Bichoy! ;)


我非常确定在for循环的执行过程中,某些数据会从设备传输到主机,然后再返回设备。有没有办法观察设备<-->主机之间的传输是否发生以及何时发生? - MadHatter
这可能不是最佳的方式。整个过程可以向量化,如下所示:pig = nnz(sum(X.^2,2)<=1) * 4 / size(X,1) 其中 X=rand(N,2) 或类似于 GPU 上的数组。 - Amro
离题了,但这里有一个很好的模拟动画:http://pastebin.com/w0N46vJE :) 类似于:http://en.wikipedia.org/wiki/File:Pi_30K.gif - Amro
欢迎您提供链接! - MadHatter
3个回答

3
我猜问题出在parfor上! parfor应该在MATLAB工作进程上运行,也就是在您的主机上而不是GPU上!我想实际发生的情况是您在主机上启动了1024个线程(而不是在GPU上),每个线程都试图调用GPU。这导致了代码所花费的巨大时间。
尝试重新编写您的代码,使用矩阵和数组操作,而不是循环!这将显示一些加速。另外,请记住,在GPU中应该有更多的计算任务,否则内存传输将支配您的代码。

代码:

这是包含所有修正和来自多个人的建议的最终代码:

function pig = mc2veccuda(n)
  A=gpuArray.rand(n,2); % An nx2 random matrix
  Asq = A.^2; % Get the square value of each element
  Anormsq = Asq(:,1)+Asq(:,2); % Get the norm squared of each point
  gpucountr = nnz(Anorm<1); % Check the number of elements < 1
  pig=(gpucountr/n)*4;

你很棒,给了我一次严肃的教训。我想你为了教育目的而留下了有点混乱的代码,请查看我的原始帖子编辑以获取更多细节!非常感谢,通过你的回答,我开始学习如何正确地向量化代码 ;) - MadHatter
我很高兴它对你有所帮助。这是这个表单的全部目的。抱歉我的代码缺乏教育性的注释,我会尝试编辑它以使其更清晰,我匆忙回复了你。 谢谢你的美言。 - Bichoy
顺便说一下,如果您认为有必要使我的帖子更加清晰明了,请随意提出任何修改建议。如果您无法进行编辑,请在此处添加评论,我会进行调整。 - Bichoy
我根据你在最后一个问题中发布的内容更正了我的答案。再次感谢 :) - Bichoy
不会,它只会获取非零元素的计数。由于您没有for循环,因此无需增加/添加。 nnz 实际上将计算非零元素并直接分配给 gpucountr。试试吧 :) - Bichoy
显示剩余2条评论

1
正如Bichoy所说,CUDA代码应该始终进行向量化处理。在MATLAB中,除非你正在编写CUDA内核,否则你得到的唯一大幅加速是在拥有数千个(慢速)核心的GPU上调用向量化操作。如果你没有大型向量和向量化代码,它将没有帮助。
另一个未提及的事情是,对于像GPU这样高度并行的体系结构,你需要使用不同于“标准”算法的随机数生成算法。因此,在Bichoy的答案中添加参数'Threefry4x64'(64位)或'Philox4x32-10'(32位且速度更快!超级快!)可以导致CUDA代码大幅加速。MATLAB在这里解释了这一点:http://www.mathworks.com/help/distcomp/examples/generating-random-numbers-on-a-gpu.html

1

有很多原因,例如:

  1. 数据在主机和设备之间的移动
  2. 每个循环内的计算非常小
  3. 在GPU上调用rand可能不是并行的
  4. 循环内的if条件可能导致分歧
  5. 对共同变量的累积可能串行运行,并带有开销

很难对Matlab+CUDA代码进行性能分析。您应该尝试使用本机C++/CUDA并使用并行Nsight查找瓶颈。


感谢您的回复。根据此帖子:https://dev59.com/vF_Va4cB1Zd3GeqPXNB3?rq=1 您是正确的。 现在,我修改了我的代码,使用nnz函数代替IF,并且r2013a支持gpuArray上的nnz。 我得到了相同可怕的结果,因此瓶颈必须在FOR循环中。 出于难以理解的原因,似乎每次迭代都会将数据来回移动,与我的信仰相反。 - MadHatter
你能否更新你的帖子并提供新的数字?我很好奇。 - Nishanth
当然:>> tic; mc2veccuda(100000); toc; 经过的时间为19.631224秒。 - MadHatter
我认为使用Matlab很难获得细粒度控制。CUDA-SDK中有许多关于执行约简操作的示例。首先在每个线程内执行求和。然后递归地对连续的线程进行求和。同时要高效地使用寄存器和本地内存/共享内存。 - Nishanth
说实话,我对C/CUDA编程还不是很熟练...不过还是非常感谢,我会学习并尝试使用C-CUDA和mex接口做些什么。 - MadHatter
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接