【问题标题】:Increasing per thread register usage in CUDA增加 CUDA 中每个线程的寄存器使用率
【发布时间】:2012-08-25 19:27:06
【问题描述】:

通常建议降低每个线程的寄存器压力以增加 warp 占用率,从而提供更大的机会通过 warp 级多线程 (TLP) 隐藏延迟。为了减少寄存器压力,可以使用更多的每个线程本地内存或每个线程块共享内存。 CUDA nvcc 编译器也可以强制每个线程使用更少的寄存器。这种方法对于具有良好算术延迟的工作负载很有用,即 ALU 操作与内存 r/w 访问请求的比率很高。然而,对于计算量很少且内存访问更频繁的延迟关键型应用程序,这种方法实际上往往会降低性能。

在此类延迟关键应用程序的情况下,将尽可能多的数据带入片上寄存器或共享内存中更有意义,然后在将其替换为来自全局的下一个数据块之前尽可能多地使用它记忆。当然,通过增加寄存器压力,warp 占用率会降低,但现在我们正在使用快速片上寄存器隐藏片外内存延迟。增加每个线程寄存器使用的方法是通过展开循环或计算每个线程的更多输出数据来增加 ILP(这也基本上通过对更多输入执行相同的工作来增加 ILP)。这种方法基本上是由 Volkov 提出的(Better Performance at Lower Occupancy)。

现在 nvcc 编译器驱动程序有一个名为 maxrregcount 的命令行选项,它允许更改每个线程的寄存器使用情况。使用此选项一次可以强制编译器减少每个线程寄存器的使用,但不能强制它增加它。我有一个案例,我想增加每个线程寄存器的使用,但我不能在内核中展开循环,因为循环边界是数据相关的和动态的。到目前为止,我已经尝试了一些技巧,但我已经没有关于如何增加每个线程寄存器使用的想法。谁能建议增加单个 CUDA 线程的寄存器使用率的方法?

【问题讨论】:

  • 对不起,这个问题对我来说似乎完全荒谬。
  • @Roger Dahl:如果你刚刚阅读了我提到的论文,你就会明白我在这里试图表达的观点

标签: memory cuda latency cpu-registers


【解决方案1】:

在某种程度上,这个问题重复了Forcing CUDA to use register for a variable。您已经很好地总结了这些选项。如果您不能通过展开和显式使用标量变量来强制使用寄存器,那么我认为您可能会被卡住。

请注意,即使是具有动态边界的循环也可以部分手动展开。您只需检查循环展开部分内的边界。这可能有助于增加寄存器使用率。

我还认为,增加寄存器使用量和减少延迟之间并不能保证直接关系,因此您实际上应该专注于减少延迟,而不是特别关注寄存器使用量。

如果您想减少整体内核延迟,那么您应该尝试一些方法。

  • 启动的线程块不能超过 GPU 上可以同时运行的线程块(由占用计算器确定)。
  • 尽量减少内核函数参数的数量,因为这些函数参数需要在内核启动期间进行初始化(并且参数过多会增加启动开销)。

【讨论】:

  • 我已经阅读了您之前的帖子。实际上我现在正在尝试手动展开。最初这似乎是不可能的,但昨晚发现了一些技巧。我实际上试图强制编译器分配更少的寄存器以获得更高的占用率,但增加占用率似乎会降低内核的性能。这就是为什么我认为增加每个线程寄存器应该可以提高性能。当然会有一个阈值,超过该阈值的占用率会影响性能。但我认为我需要探索这种权衡。
  • 我正在使用 Tesla C1060,我的每个线程寄存器使用量为 32,这将占用率限制为 50% 或每个 SM 两个线程块。正如我所看到的,当我使用更多线程块(每个 SM 4 个线程块而不是 2 个)启动内核时,我会得到更好的结果。目前我的内核中的参数数量约为 15,我猜这太高了。也许我需要连接几个数组和一半的参数。
【解决方案2】:

有趣的问题!我也在尝试这种使用 ILP 的方法来提供更好的性能!事实上,由于我受制于较旧的 GPU 架构,每个线程分配的寄存器较少,因此使用 ILP 实际上可以提高性能,因为它通过循环展开(独立指令)释放寄存器以进行更多计算工作!

我想知道你有多少个嵌套循环?如果内部循环无法展开,可能会上一层并寻找机会?

为了增加每个线程的寄存器使用量,您是否减少了启动的块数(使用较少的线程)?
要增加寄存器/线程的使用率,请加载一组以上的数据以并行执行。

在循环的每次迭代中它是独立的吗?我相信关键是寻找独立的计算。 批量执行如何。假设循环计数为 N,将其拆分为 N/M 并独立地对它们进行 omcpute?

当你给出一点线索时,很难给出建议:P

【讨论】:

  • 我也在使用旧的 Tesla Architecture 1.3 计算能力。我的顺序算法有四级循环嵌套。实现 CUDA 内核后,我通过并行化外部两个循环将它们减少到两个。我无法展开内循环,现在我正专注于手动展开外循环。我基本上采用每次外循环迭代产生更多输出的方法,因为每个循环迭代都是独立的。我考虑过平铺方法,但在我的情况下它不起作用。
  • 有任何改进的更新吗? =) 在我这边,我尝试移植到 Kepler 架构 GPU,但加速并不那么显着。 Kepler 现在为每个线程分配了更多的寄存器。
  • 我继续使用 Tesla C1060。我也无法获得任何加速。我尝试了 2x 和 3x 展开,寄存器使用量从最初的 31 增加到 41 再到 52,但不幸的是性能没有太大改善。但无论如何,我的内核已经平均加速了 32 倍,最大加速了 56 倍。
【解决方案3】:

这个问题的表述方式就像是在问,“我怎样才能在商店里花更多的钱买牛奶?”问题是颠倒的。你应该问的是,“我有一定数量的钱。我如何用它来获得尽可能多的牛奶?”

好吧,不是最好的类比,但基本上,问题的表述好像增加寄存器数量本身就是目标,而目标当然是提高性能。

所以,首先要确定的是,您的寄存器数量是否与您认为的一样多?如果寄存器是内核中的占用限制因素。当内核受内存限制时,更改代码以使用更多寄存器可能不是一个好主意。

如果您确定占用受到其他因素的限制,那么您可以询问是否可以通过使用更多寄存器来提高性能(然后寄存器“空闲”,直到寄存器成为占用限制因素)。

为此,您然后开始查看Space–time tradeoffs 的选项。

【讨论】:

  • 我认为您关于入住率的 cmets 被误导了。 nurav 不需要减少他的占用限制资源,因为增加占用可能会产生他想要的完全相反的效果。它可能会增加延迟。他想减少延迟,而不是增加吞吐量。增加占用率并不能减少延迟。在一定程度上(当有空闲槽要填充时),增加占用率不会增加延迟,但是一旦 SM 完成每个周期的一条指令,进一步增加只会增加延迟。
  • @harrism:完全正确。增加内核的占用率实际上会降低性能。增加入住率只能将延迟减少到一个点
  • nurava 和@harrism:感谢您提供的信息和反馈。我已经阅读了这篇论文,现在明白了你在说什么。它违背了我认为我知道的事情,但完全有道理。我可能会考虑自己在未来的内存绑定内核中应用这些技术。
猜你喜欢
  • 2013-07-07
  • 1970-01-01
  • 1970-01-01
  • 2018-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-17
  • 1970-01-01
相关资源
最近更新 更多