在 cuda 线程之间共享大量常量数据
Posted
技术标签:
【中文标题】在 cuda 线程之间共享大量常量数据【英文标题】:Share large constant data among cuda threads 【发布时间】:2019-04-08 12:08:12 【问题描述】:我有一个被多次调用的内核。在每次调用中,大约 240 KB 的常量数据将由线程共享和处理。线程像地图函数一样独立工作。线程的停顿时间相当长。其背后的原因可能是内存读取的银行冲突。我该如何处理?(我有 GTX 1080 ti)
opencl 的“const global”可以处理这个吗? (因为 cuda 中的常量内存限制为 64 kb)
【问题讨论】:
银行冲突极不可能对您所看到的情况负责。提出对策需要对您的用例进行详细描述,而您未能提供 您可以将访问索引打包到一个均匀递增的数组中,并重新排序工作项以适应它们,以便合并访问? 【参考方案1】:在 CUDA 中,我认为最好的建议是使用所谓的“只读”缓存。与__constant__
内存/常量缓存系统相比,这至少有两个可能的好处:
-
不限于像
__constant__
内存那样的64kB。
它不期望或需要像常量缓存那样的“统一访问”来提供完整的访问带宽/最佳性能。统一访问指的是,warp 中的所有线程都在访问相同的位置或相同的常量内存值(每个读取周期/指令)。
只读缓存记录在the CUDA programming guide 中。可能最简单的使用方法是使用__restrict__
(假设您是not aliasing between pointers)将decorate your pointers 传递给CUDA 内核,并使用const ... __restrict__
装饰指向大型常量数据的指针。这将允许编译器生成适当的 LDG 指令以访问常量数据,并通过只读缓存机制将其拉出。
这种只读缓存机制仅在 cc 3.5 或更高版本的 GPU 上受支持,但它涵盖了 Kepler 一代中的一些 GPU 以及 Maxwell、Pascal(包括您的 GTX 1080 ti)、Volta 和 Turing 一代中的所有 GPU。
如果您的 GPU 小于 cc3.5,那么获得类似好处(大于 __const__
,不需要统一访问)的最佳建议可能是使用纹理内存。这也记录在编程指南的其他地方,有各种 CUDA 示例代码演示了纹理内存的使用,还有很多关于 SO cuda
标签的问题。
【讨论】:
在 Windows 上使用__restrict__
时出现 nvcc 错误。有没有跨平台的方式来使用它?
那你做错了。 __restrict__
在 windows 和 linux 上都被 nvcc 接受。拜托,我们不要试图在这里解决它。如果您有新问题,请提出新问题。 nbody
和 p2pBandwidthLatencyTest
cuda 示例项目都有 __restrict__
装饰内核。您可能希望研究这些项目并在您的 Windows 设置上编译它们。
"这种只读缓存机制仅在 ... Volta 和 Turing 世代上受支持。" 您能否确认这对 Volta 和 Turing 是正确的?从this paper的第3章开始,Volta和Turing的内存层次结构中没有只读缓存。
我不知道那篇论文,但我在答案中链接的 NVIDIA 文档表明:1. cc3.5 及更高版本的 GPU 支持只读功能(不排除for 7.x) 更进一步: 2. 7.x 的全局内存描述表示与5.x 相同,5.x 的全局内存描述包括只读缓存的具体描述。在 cc7.x 的情况下,只读缓存功能完全有可能与 GPU 中的其他一些功能单元合并,但这并不意味着该功能不存在。【参考方案2】:
不适合硬件常量缓冲区的常量内存通常会“溢出”到 OpenCL 上的global
内存中。然而,银行冲突通常是 local
内存的问题,所以可能不是这样。我假设 CUDA 的 64kiB 常量限制反映了 nvidia 的硬件,所以 OpenCL 在这里不会神奇地表现得更好。
在没有可预测模式的情况下读取全局内存当然会很慢,但是,特别是如果您没有足够的线程占用和算术来掩盖内存延迟。
在不进一步了解您的问题空间的情况下,这也将我带到了您可以采取进一步优化的方向,假设您的全局内存读取是问题:
减少所需的常量/全局数据量,例如通过使用更高效的类型、其他压缩机制或动态计算某些值(可能将它们存储在local
内存中以供组中的所有线程使用分享)。
将最常用的数据聚集在一个小的constant
缓冲区中,并将较少使用的常量明确放置在一个global
缓冲区中。这可能有助于运行时更有效地在硬件中进行布局。如果这没有帮助,请尝试将常用数据复制到 local
内存中,并使您的线程组较大且运行时间较长以隐藏复制命中。
检查是否可以提高线程占用率。它通常可以,这往往会在几乎任何情况下为您带来显着的性能改进。 (除非您的代码已经非常受 ALU/FPU 限制)
【讨论】:
是否有任何文档描述持续内存溢出到全局? @AntiEarth 例如,在AMD's OpenCL optimisation guide,第 3.3 节:“1. 全局范围的常量数组。这些数组是初始化的,全局范围的,并且在常量地址空间 (…)。如果大小数组的大小低于 64 kB,它被放置在硬件常量缓冲区中;否则,它使用全局内存。这方面的一个例子是数学函数的查找表。和“2. 指定最大指针大小的每个指针属性。这是使用 max_constant_size(N) 属性指定的……” @AntiEarth 我没有找到任何关于 nvidia 实现行为的明确文档,但似乎这些 GPU 可以缓存高达 64KiB 的恒定内存。所以我想这取决于你在那里的访问模式。 啊,非常感谢!是的,我怀疑这发生在 CUDA 中,这是一种耻辱以上是关于在 cuda 线程之间共享大量常量数据的主要内容,如果未能解决你的问题,请参考以下文章