Chinese, Simplified subtitles

← cs344 unit3 11b_q_用全局和共享内存归约

Get Embed Code
2 Languages

Showing Revision 2 created 05/04/2013 by Michael Xiao.

  1. 我们已完成了所有准备工作,那么让我们转向一些真正的代码。
  2. 我们将执行这个代码两次,每次的策略相似。
  3. 两次中我们将执行一百万个元素的求和,实际上是2^20个元素,
  4. 我们将分两个阶段进行。
  5. 第一阶段,我们启动1024个块,
  6. 每一个块用1024个线程归约1024个元素。
  7. 每一个块将生成1个单项。
  8. 所以当我们完成时,会剩下1024项。
  9. 我们将启动一个块把最后的1024个元素归约到一个单一元素。
  10. 当然我会贴上所有代码。不过,CPU部分的代码很简单。
  11. 因此,我们只看一下内核。让我们看看那是如何工作的。
  12. 每个块负责一个有1024个元素的浮点块,
  13. 我们将在内核中运行这个循环。
  14. 每次循环迭代,我们会把活动区分成两半。
  15. 第1次迭代中,我们开始时有1024个元素,
  16. 我们将有两个512元素区。
  17. 然后512个线程中的每一个会把第2个半区的元素加到第1个半区,
  18. 写回到第一个半区。
  19. 现在我们将同步所有线程,同步线程调用就在这,
  20. 用来确保所有线程都已完成。
  21. 我们还剩有512个元素,
  22. 我们将对这个512元素的结果区再次循环。
  23. 现在我们把它分成两个256元素块,用256个线程
  24. 把这256项加到这256个项。
  25. 我们将继续这个循环,每次分成两半,
  26. 直到10次迭代的最后我们只剩1个元素。
  27. 然后我们把这个元素写回全局内存。这是可行的的。
  28. 我们可以在我们实验室的计算机上运行它。
  29. 我们现在就在运行,我们注意到它在0.65毫秒内就完成了。
  30. 少于1毫秒。这相当好,但它还没有达到我们想要的效率。
  31. 特别地,如果我们再看一下代码,
  32. 我们进入全局内存的频率比我们希望的更频繁。
  33. 每次循环迭代,我们从全局内存读取 n 项,写回 n/2 项。
  34. 然后,我们再从全局内存读回那 n/2 项,等等。
  35. 在理想情况下,我们进行一次原始读取,
    即我们把全部1024项读入线程块,
  36. 在内部进行所有归约,然后把最终值写回。
  37. 这应该更快,因为总体上我们引发较少的存储流量。
  38. 我们用来实现这个的CUDA特征叫做共享内存,
  39. 所有中间值都存储到所有线程都可以访问的共享内存。
  40. 共享内存的速度远远超过全局内存。
  41. 让我们看看内核,它将看起来很相似。
  42. 在这个内核中,我们将用完全相同的循环结构。
  43. 不同的是这里的一小部分。
  44. 我们首先要把所有值从全局内存值复制到共享内存,
  45. 用这一小块完成。
  46. 然后,这里所有后续的访问都是从共享内存——
  47. 这个 s 数据——与我们上次做的从全局内存访问形成对照。
  48. 当我们完成后,我们得再次把这个最终值写回全局内存。
  49. 这个代码另一个有趣的部分是
    我们如何声明我们需要的共享内存数量。
  50. 我们在这进行。
  51. 我们声明我们将有一个外部定义的共享数据量。
  52. 现在,我们还没有确切地说我们需要多少。
  53. 所以要做到这一点,我们得往下到我们真正调用内核的地方。
  54. 当我们用共享内存调用归约内核,
  55. 我们用<<< >>>内的3个参数调用它,正常块和线程,
  56. 以及我们说我们需要在共享内存分配多少字节。
  57. 这种情况下,每一线程将要求存储在共享内存中的1个浮点。
  58. 共享内存代码版本的优点是它节省了全局内存带宽。
  59. 弄清楚你将节省多少内存带宽是个很好的练习。
  60. 我把那作为一个测验。
  61. 全局内存代码版本使用的内存带宽是
  62. 共享内存代码版本的多少倍?
  63. 四舍五入到整数。