0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

通过NVIDIA GPU内存预取实现应用程序性能的提高

星星科技指导员 来源:NVIDIA 作者:NVIDIA 2022-04-02 16:33 次阅读

NVIDIA GPU 具有强大的计算能力,通常必须以高速传输数据才能部署这种能力。原则上这是可能的,因为 GPU 也有很高的内存带宽,但有时他们需要你的帮助来饱和带宽。

在本文中,我们将研究一种实现这一点的特定方法:预取。我们将解释在什么情况下预取可以很好地工作,以及如何找出这些情况是否适用于您的工作负载。

上下文

NVIDIA GPU 从大规模并行中获得力量。 32 个线程的许多扭曲可以放置在流式多处理器( SM )上,等待轮到它们执行。当一个 warp 因任何原因暂停时, warp 调度程序会以零开销切换到另一个,确保 SM 始终有工作要做。

在高性能的 NVIDIA Ampere 架构 A100 GPU 上,多达 64 个活动翘板可以共享一个 SM ,每个都有自己的资源。除此之外, A100 还有 108 条短信,可以同时执行 warp 指令。

大多数指令都必须对数据进行操作,而这些数据几乎总是源于连接到 GPU 的设备内存( DRAM )。 SM 上大量的翘曲都可能无法工作的一个主要原因是,它们正在等待来自内存的数据。

如果出现这种情况,并且内存带宽没有得到充分利用,则可以重新组织程序,以改善内存访问并减少扭曲暂停,从而使程序更快地完成。这叫做延迟隐藏。

预取

CPU 上的硬件通常支持的一种技术称为预取。 CPU 看到来自内存的请求流到达,找出模式,并在实际需要数据之前开始获取数据。当数据传输到 CPU 的执行单元时,可以执行其他指令,有效地隐藏传输成本(内存延迟)。

预取是一种有用的技术,但就芯片上的硅面积而言很昂贵。相对而言, GPU 的这些成本甚至更高,因为 GPU 的执行单元比 CPU 多得多。相反, GPU 使用多余的扭曲来隐藏内存延迟。当这还不够时,可以在软件中使用预取。它遵循与硬件支持的预取相同的原理,但需要明确的指令来获取数据。

要确定此技术是否能帮助您的程序更快地运行,请使用 GPU 评测工具(如 NVIDIA Nsight Compute )检查以下内容:

确认没有使用所有内存带宽。

确认翘曲被阻止的主要原因是 摊位长记分牌 ,这意味着 SMs 正在等待来自 DRAM 的数据。

确认这些暂停集中在迭代互不依赖的大型循环中。

展开

考虑这种循环的最简单可能的优化,称为展开。如果循环足够短,可以告诉编译器完全展开循环,并显式展开迭代。由于迭代是独立的,编译器可以提前发出所有数据请求(“加载”),前提是它为每个加载分配不同的寄存器

这些请求可以相互重叠,因此整个负载集只经历一个内存延迟,而不是所有单个延迟的总和。更妙的是,加载指令本身的连续性隐藏了单个延迟的一部分。这是一种接近最优的情况,但可能需要大量寄存器才能接收加载结果。

如果循环太长,可能会部分展开。在这种情况下,成批的迭代会被扩展,然后您会遵循与之前相同的一般策略。你的工作很少(但你可能没那么幸运)。

如果循环包含许多其他指令,这些指令的操作数需要存储在寄存器中,那么即使只是部分展开也可能不是一个选项。在这种情况下,在您确认满足之前的条件后,您必须根据进一步的信息做出一些决定。

预取意味着使数据更接近 SMs 的执行单元。寄存器是最接近的。如果有足够的可用空间(可以使用 Nsight Compute Occupation 视图找到),可以直接预取到寄存器中。

考虑下面的循环,其中数组arr被存储在全局存储器( DRAM )中。它隐式地假设只使用了一个一维线程块,而对于从中派生的激励应用程序来说,情况并非如此。然而,它减少了代码混乱,并且不会改变参数

在本文的所有代码示例中,大写变量都是编译时常量。BLOCKDIMX假定预定义变量blockDim.x的值。出于某些目的,它必须是编译时已知的常数,而出于其他目的,它有助于避免在运行时进行计算。

for (i=threadIdx.x; i
};>

假设您有八个寄存器用于预取。这是一个调整参数。下面的代码在每四次迭代开始时获取四个双精度值,占据八个 4 字节寄存器,并逐个使用它们,直到批耗尽,此时您将获取一个新批。

为了跟踪批处理,引入一个计数器(ctr),该计数器随着线程执行的每个后续迭代而递增。为了方便起见,假设每个线程的迭代次数可以被 4 整除。

double v0, v1, v2, v3;
for (i=threadIdx.x, ctr=0; i
};>

通常,预取的值越多,该方法就越有效。虽然前面的例子并不复杂,但有点麻烦。如果预取值(PDIST或预取距离)的数量发生变化,则必须添加或删除代码行。

将预取值存储在共享内存中更容易,因为您可以使用数组表示法,无需任何努力就可以改变预取距离。然而,共享内存并不像寄存器那样接近执行单元。当数据准备好使用时,它需要一条额外的指令将数据从那里移动到寄存器中。为了方便起见,我们引入宏vsmem来简化共享内存中数组的索引

#define vsmem(index) v[index+PDIST*threadIdx.x]
__shared__ double v[PDIST* BLOCKDIMX];
for (i=threadIdx.x, ctr=0; i
};>

除了批量预取,还可以进行“滚动”预取。在这种情况下,在进入主循环之前填充预取缓冲区,然后在每次循环迭代期间从内存中预取一个值,以便在以后的PDIST迭代中使用。下一个示例使用数组表示法和共享内存实现滚动预取。

__shared__ double v[PDIST* BLOCKDIMX];
for (k=0; k
};>

与批处理方法相反,滚动预取在主循环执行期间不会再出现足够大的预取距离的内存延迟。它还使用相同数量的共享内存或寄存器资源,因此它似乎是首选。然而,一个微妙的问题可能会限制其有效性。

循环中的同步(例如,syncthreads)构成了一个内存围栏,并迫使arr的加载在同一迭代中的该点完成,而不是在以后的 PDIST 迭代中完成。解决方法是使用异步加载到共享内存中,最简单的版本在 CUDA 程序员指南的 Pipeline interface 部分中解释。这些异步加载不需要在同步点完成,只需要在显式等待时完成。

以下是相应的代码:

#include 
__shared__ double v[PDIST* BLOCKDIMX];
for (k=0; k
};>

由于每一条__pipeline_wait_prior指令都必须与一条__pipeline_commit指令匹配,我们在进入主计算循环之前,将后者放入预取缓冲区的循环中,以简化匹配指令对的簿记。

绩效结果

图 1 显示,对于不同的预取距离,在前面描述的五种算法变化下,从金融应用程序中获取的内核的性能改进。

分批预取到寄存器(标量分批)

分批预取到共享内存( smem 分批)

将预取滚动到寄存器(标量滚动)

将预取滚动到共享内存( smem 滚动)

使用异步内存拷贝将预取滚动到共享内存( smem 滚动异步)

Graph shows that smem rolling async speeds up by -60% at a distance of 6.Graph shows that smem rolling async speeds up by -60% at a distance of 6.

图 1 。不同预取策略的内核加速

显然,将预取滚动到具有异步内存拷贝的共享内存中会带来很好的好处,但随着预取缓冲区大小的增加,这是不均匀的。

使用 Nsight Compute 对结果进行更仔细的检查后发现,共享内存中会发生内存组冲突,这会导致异步负载的扭曲被拆分为比严格必要的更连续的内存请求。经典的优化方法是在共享内存中填充数组大小,以避免错误的跨步,这种方法在这种情况下有效。PADDING的值的选择应确保PDIST和PADDING之和等于二加一的幂。将其应用于所有使用共享内存的变体:

#define vsmem(index) v[index+(PDIST+PADDING)*threadIdx.x]

这导致图 2 所示的共享内存结果得到改善。预取距离仅为 6 ,再加上以滚动方式进行的异步内存拷贝,就足以以比原始版本代码近 60% 的加速比获得最佳性能。实际上,我们可以通过更改共享内存中数组的索引方案来实现这种性能改进,而无需使用填充,这是留给读者的练习。Graph shows speedup percentages where scalar rolling alone slows performance by ~60% and other rolling/batched strategies shows speedups of 20-30%.Graph shows speedup percentages where scalar rolling alone slows performance by ~60% and other rolling/batched strategies shows speedups of 20-30%.

图 2 。使用共享内存填充的不同预取策略的内核加速

一个尚未讨论的 预取的变化 将数据从全局内存移动到二级缓存,如果共享内存中的空间太小,无法容纳所有符合预取条件的数据,这可能很有用。这种类型的预取在 CUDA 中无法直接访问,需要在较低的 PTX 级别进行编程

总结

在本文中,我们向您展示了源代码的本地化更改示例,这些更改可能会加快内存访问。这些不会改变从内存移动到 SMs 的数据量,只会改变时间。通过重新安排内存访问,使数据在到达 SM 后被多次重用,您可以进行更多优化。

关于作者

Rob Van der Wijngaart 是 NVIDIA 的高级高性能计算( HPC )架构师。他在各种工业和政府实验室从事 HPC 领域的研究超过三十年,是广泛使用的 NAS 并行基准测试的共同开发者。Ren é Peters 是 NVIDIA 的产品经理,他在增强/虚拟现实和人工智能的交叉点指导产品开发。在科技行业任职期间,他还与物联网IoT )和云计算等技术合作。迈尔斯·麦克林( Miles Macklin )是NVIDIA 的首席工程师,致力于模拟技术。他从哥本哈根大学获得计算机科学博士学位,从事计算机图形学、基于物理学的动画和机器人学的研究。他在 ACM SIGGRAPH 期刊上发表了几篇论文,他的研究已经被整合到许多商业产品中,包括NVIDIA 的 PhysX 和 ISAAC 健身房模拟器。他最近的工作旨在为 GPU 上的可微编程开发健壮高效的框架。

Fred Oh 是 CUDA 、 CUDA on WSL 和 CUDA Python 的高级产品营销经理。弗雷德拥有加州大学戴维斯分校计算机科学和数学学士学位。他的职业生涯开始于一名 UNIX 软件工程师,负责将内核服务和设备驱动程序移植到 x86 体系结构。他喜欢《星球大战》、《星际迷航》和 NBA 勇士队。

审核编辑:郭婷

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储器
    +关注

    关注

    38

    文章

    7120

    浏览量

    161918
  • NVIDIA
    +关注

    关注

    14

    文章

    4579

    浏览量

    101639
  • gpu
    gpu
    +关注

    关注

    27

    文章

    4403

    浏览量

    126563
收藏 人收藏

    评论

    相关推荐

    微基准测试的性能特征及如何在应用程序中使用统一内存

      从 NVIDIA Pascal 更容易扩展到更大的问题规模体系结构开始,支持统一内存应用程序可以使用系统 CPU 中所有可用的 CPU 和 GPU
    的头像 发表于 04-18 17:40 4708次阅读
    微基准测试的<b class='flag-5'>性能</b>特征及如何在<b class='flag-5'>应用程序</b>中使用统一<b class='flag-5'>内存</b>

    NVIDIA火热招聘GPU性能计算架构师

    这边是NVIDIA HR Allen, 我们目前在上海招聘GPU性能计算架构师(功能验证)的岗位,有意向的朋友欢迎发送简历到 allelin@nvidia
    发表于 09-01 17:22

    NVIDIA-SMI:监控GPU的绝佳起点

    nvidia-smi可执行文件位于虚拟机管理程序上。如果在同一部署中您选择在GPU上使用作为传递,那么GPU正在寻找访客上的NVIDIA驱动
    发表于 09-04 15:18

    GPU加速XenApp/Windows 2016/Office/IE性能提高

    7.14 -NVIDIA vGPU Manager(384.73) - 适用于OS的NVIDIA驱动程序(385.41)我也对在XenApp中测试/验证GPU使用/
    发表于 09-12 16:24

    探求NVIDIA GPU极限性能的利器

    1、探求 NVIDIA GPU 极限性能的利器  在通常的 CUDA 编程中,用户主要通过 CUDA C/C++ 或 python 语言实现
    发表于 10-11 14:35

    Cortex-R82的器功能分析

    性能处理器采用硬件数据取来减少大的主内存延迟对性能的负面影响。有效的机制可以显著
    发表于 08-09 06:11

    利用矢量硬件如何提高应用程序性能

    本次会议演示了识别和修改代码以利用矢量硬件的过程如何提高应用程序性能
    的头像 发表于 05-31 11:46 1094次阅读

    近600个应用程序通过NVIDIA GPU实现了提速

    十几年前,还不曾有加速应用程序。而如今已有近600个应用程序通过NVIDIA GPU实现了提速。
    的头像 发表于 02-14 14:15 4525次阅读

    LabVIEW应用程序性能瓶颈的解决

    了解如何识别和解决LabVIEW应用程序中的性能瓶颈。使用内置工具和VI分析器,您可以监视VIs的内存使用情况和执行时间,以确定导致应用程序性能下降的代码部分。
    发表于 03-29 14:03 8次下载
    LabVIEW<b class='flag-5'>应用程序</b>中<b class='flag-5'>性能</b>瓶颈的解决

    使用NVIDIA TensorRT部署实时深度学习应用程序

    深度神经网络 (DNN) 是实现强大的计算机视觉和人工智能应用程序的强大方法。今天发布的NVIDIA Jetpack 2.3使用 NVIDIA TensorRT (以前称为
    的头像 发表于 04-18 14:28 1836次阅读
    使用<b class='flag-5'>NVIDIA</b> TensorRT部署实时深度学习<b class='flag-5'>应用程序</b>

    如何使用NVIDIA Docker部署GPU服务器应用程序

    管理工作流程的方式。使用 Docker ,我们可以在工作站上开发和原型化 GPU 应用程序,然后在任何支持 GPU 容器的地方发布和运行这些应用程序
    的头像 发表于 04-27 15:06 2228次阅读
    如何使用<b class='flag-5'>NVIDIA</b> Docker部署<b class='flag-5'>GPU</b>服务器<b class='flag-5'>应用程序</b>

    通过GPU内存访问调整提高应用程序性能

    在本文的所有代码示例中,大写变量都是编译时常量。 BLOCKDIMX 采用预定义变量 blockDim 的值。 x 、 出于某些目的,它必须是编译时已知的常量,而出于其他目的,它有助于避免在运行时进行计算。
    的头像 发表于 08-15 16:24 1270次阅读

    通过32Gb/S光纤通道提高应用程序性能

    电子发烧友网站提供《通过32Gb/S光纤通道提高应用程序性能.pdf》资料免费下载
    发表于 07-29 09:56 0次下载
    <b class='flag-5'>通过</b>32Gb/S光纤通道<b class='flag-5'>提高</b><b class='flag-5'>应用程序性能</b>

    使用Brocade Gen 7 SAN确保应用程序性能和可靠性

    电子发烧友网站提供《使用Brocade Gen 7 SAN确保应用程序性能和可靠性.pdf》资料免费下载
    发表于 09-01 10:51 0次下载
    使用Brocade Gen 7 SAN确保<b class='flag-5'>应用程序性能</b>和可靠性

    PGO到底是什么?PGO如何提高应用程序性能呢?

    PGO到底是什么?PGO如何提高应用程序性能呢? PGO,全称为Profile Guided Optimization,译为“基于特征优化”的技术,是一种通过利用应用程序的运行特征数据
    的头像 发表于 10-26 17:37 1496次阅读