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

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

3天内不再提示

如何在CUDA C/C++中实现主机和设备同步执行

星星科技指导员 来源:NVIDIA 作者:Mark Harris 2022-04-11 10:26 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

在 本系列文章的第一篇 中,我们通过检查 CUDA C/C++ SAXPY 来研究 CUDA C / C ++的基本元素。在第二篇文章中,我们将讨论如何分析这个和其他 CUDA C / C ++代码的性能。我们将依赖于这些性能测量技术在未来的职位,性能优化将变得越来越重要。

CUDA 性能度量通常是从主机代码中完成的,可以使用 CPU 计时器或 CUDA 特定计时器来实现。在讨论这些性能度量技术之前,我们需要讨论如何在主机和设备之间同步执行。

主机设备同步

让我们看看数据传输和来自上一篇文章的 SAXPY 主机代码的内核启动:

cudaMemcpy(d_x, x, N*sizeof(float), cudaMemcpyHostToDevice);

cudaMemcpy(d_y, y, N*sizeof(float), cudaMemcpyHostToDevice);



saxpy<<<(N+255)/256, 256>>>(N, 2.0, d_x, d_y);



cudaMemcpy(y, d_y, N*sizeof(float), cudaMemcpyDeviceToHost);

使用cudaMemcpy()在主机和设备之间的数据传输是synchronous(或blocking)传输。同步数据传输在之前发出的所有 CUDA 调用完成之前不会开始,后续的 CUDA 调用在同步传输完成之前无法开始。因此,第三行的saxpy内核启动在第二行从yd_y的传输完成后才会发出。另一方面,内核启动是异步的。一旦内核在第三行启动,控制权立即返回到 CPU ,而不是等待内核完成。而 MIG ht 似乎为设备在最后一行主机数据传输设置了一个竞争条件,数据传输的阻塞性质确保了内核在传输开始之前完成。

用 CPU 计时器计时内核执行

现在让我们来看看如何使用 CPU 计时器为内核执行计时。

cudaMemcpy(d_x, x, N*sizeof(float), cudaMemcpyHostToDevice);

cudaMemcpy(d_y, y, N*sizeof(float), cudaMemcpyHostToDevice);



t1 = myCPUTimer();

saxpy<<<(N+255)/256, 256>>>(N, 2.0, d_x, d_y);

cudaDeviceSynchronize();

t2 = myCPUTimer();



cudaMemcpy(y, d_y, N*sizeof(float), cudaMemcpyDeviceToHost);

除了对通用主机时间戳函数myCPUTimer()的两次调用外,我们还使用显式同步屏障cudaDeviceSynchronize()来阻止 CPU 的执行,直到设备上以前发出的所有命令都已完成。如果没有这个屏障,这段代码将测量内核发射时间,而不是内核执行时间

使用 CUDA 事件计时

使用主机设备同步点(如cudaDeviceSynchronize()的一个问题是它们会暂停 GPU 管道。因此, CUDA 通过CUDA 事件 API为 CPU 定时器提供了一个相对轻量级的替代方案。 CUDA 事件 API 包括在两个记录的事件之间调用create破坏事件、record事件和以毫秒为单位计算已用时间

CUDA 事件利用 CUDA streams. CUDA 流只是按顺序在设备上执行的操作序列。在某些情况下[vx3 . 4 可以交叉使用 vx3 . 4]的流。到目前为止, GPU 上的所有操作都发生在默认流或流 0 (也称为“空流”)中。

在下面的清单中,我们将 CUDA 事件应用于 SAXPY 代码。

cudaEvent_t start, stop;

cudaEventCreate(&start);

cudaEventCreate(&stop);



cudaMemcpy(d_x, x, N*sizeof(float), cudaMemcpyHostToDevice);

cudaMemcpy(d_y, y, N*sizeof(float), cudaMemcpyHostToDevice);



cudaEventRecord(start);

saxpy<<<(N+255)/256, 256>>>(N, 2.0f, d_x, d_y);

cudaEventRecord(stop);



cudaMemcpy(y, d_y, N*sizeof(float), cudaMemcpyDeviceToHost);



cudaEventSynchronize(stop);

float milliseconds = 0;

cudaEventElapsedTime(&milliseconds, start, stop);

CUDA 事件属于cudaEvent_t类型,使用cudaEventCreate()cudaEventDestroy()创建和销毁事件。在上面的代码中cudaEventRecord()将启动和停止事件放入默认流 stream 0 。当事件到达流中的事件时,设备将记录事件的时间戳。函数cudaEventSynchronize()会阻止 CPU 的执行,直到记录指定的事件为止。cudaEventElapsedTime()函数在第一个参数中返回录制startstop之间经过的毫秒数。该值的分辨率约为半微秒。

内存带宽

现在我们有了一种精确计时内核执行的方法,我们将使用它来计算带宽。在评估带宽效率时,我们同时使用理论峰值带宽和观察到的或有效的内存带宽。

理论带宽

理论带宽可以使用产品文献中提供的硬件规格计算。例如, NVIDIA Tesla M2050 GPU 使用内存时钟速率为 1546 MHz 的 DDR (双数据速率) RAM 和 384 位宽的内存接口。使用这些数据项, NVIDIA Tesla M2050 的峰值理论内存带宽为 148 GB / s ,如下所示。

BWTheoretical= 1546 * 106* (384 / 8) * 2 / 109= 148 GB / s

在这个计算中,我们将内存时钟速率转换为赫兹,乘以接口宽度(除以 8 ,将位转换为字节),再乘以 2 ,这是由于数据速率加倍。最后,我们除以 109将结果转换为 GB / s 。

有效带宽

我们通过计时特定的程序活动和了解程序如何访问数据来计算有效带宽。我们用下面的等式。

BWEffective=(RB+WB( VZX50]* 109)

这里,BWEffective有效带宽,单位为 GB / s ,RB是每个内核读取的字节数,WB是每个内核写入的字节数,t是以秒为单位的运行时间。下面是完整的代码。

#include



__global__

void saxpy(int n, float a, float *x, float *y)

{

  int i = blockIdx.x*blockDim.x + threadIdx.x;

  if (i < n) y[i] = a*x[i] + y[i];

}



int main(void)

{

  int N = 20 * (1 << 20);

  float *x, *y, *d_x, *d_y;

  x = (float*)malloc(N*sizeof(float));

  y = (float*)malloc(N*sizeof(float));



  cudaMalloc(&d_x, N*sizeof(float));

  cudaMalloc(&d_y, N*sizeof(float));



  for (int i = 0; i < N; i++) {

    x[i] = 1.0f;

    y[i] = 2.0f;

  }



  cudaEvent_t start, stop;

  cudaEventCreate(&start);

  cudaEventCreate(&stop);



  cudaMemcpy(d_x, x, N*sizeof(float), cudaMemcpyHostToDevice);

  cudaMemcpy(d_y, y, N*sizeof(float), cudaMemcpyHostToDevice);



  cudaEventRecord(start);



  // Perform SAXPY on 1M elements

  saxpy<<<(N+511)/512, 512>>>(N, 2.0f, d_x, d_y);



  cudaEventRecord(stop);



  cudaMemcpy(y, d_y, N*sizeof(float), cudaMemcpyDeviceToHost);



  cudaEventSynchronize(stop);

  float milliseconds = 0;

  cudaEventElapsedTime(&milliseconds, start, stop);



  float maxError = 0.0f;

  for (int i = 0; i < N; i++) {

    maxError = max(maxError, abs(y[i]-4.0f));

  }



  printf("Max error: %fn", maxError);

  printf("Effective Bandwidth (GB/s): %fn", N*4*3/milliseconds/1e6);

}

在带宽计算中,N*4是每个数组读或写传输的字节数, 3 的因子表示x的读取和y的读写。经过的时间存储在变量milliseconds中,以明确单位。请注意,除了添加带宽计算所需的功能外,我们还更改了数组大小和线程块大小。在 Tesla M2050 上编译并运行此代码:

$ ./saxpy

Max error: 0.000000

Effective Bandwidth (GB/s): 110.374872

测量计算吞吐量

我们刚刚演示了如何测量带宽,带宽是数据吞吐量的度量。另一个对性能非常重要的指标是计算吞吐量。计算吞吐量的常用度量是 GFLOP / s ,它代表“每秒千兆浮点运算”,其中 Giga 是 10 的前缀9. 我们通常测量 SAXPY 的吞吐量,因为每一个 SAXPY 运算都是有效的

GFLOP/s Effective== 2 N /( t :《* 109)

N 是 SAXPY 操作中的元素数, t 是以秒为单位的运行时间。与理论峰值带宽一样,理论峰值 GFLOP / s 可以从产品文献中获得(但是计算它可能有点棘手,因为它与体系结构非常相关)。例如, Tesla M2050 GPU 的单精度浮点吞吐量理论峰值为 1030 GFLOP / s ,双倍精度的理论峰值吞吐量为 515 GFLOP / s 。

SAXPY 为计算的每个元素读取 12 个字节,但是只执行一个乘法加法指令( 2 个浮点运算),因此很明显它是带宽受限的,因此在这种情况下(实际上在许多情况下),带宽是衡量和优化的最重要的指标。在更复杂的计算中,在 FLOPs 级别测量性能可能非常困难。因此,更常见的是使用分析工具来了解计算吞吐量是否是一个瓶颈。应用程序通常提供特定于问题(而不是特定于体系结构)的吞吐量指标,因此对用户更有用。例如,天文 n 体问题的“每秒十亿次相互作用”,或分子动力学模拟的“每天纳秒”。

总结

这篇文章描述了如何使用 CUDA 事件 API 为内核执行计时。 CUDA 事件使用 GPU 计时器,因此避免了与主机设备同步相关的问题。我们提出了有效带宽和计算吞吐量性能指标,并在 SAXPY 内核中实现了有效带宽。很大一部分内核是内存带宽限制的,因此计算有效带宽是性能优化的第一步。在以后的文章中,我们将讨论如何确定带宽、指令或延迟是性能的限制因素。

CUDA 事件还可以用于确定主机和设备之间的数据传输速率,方法是在 cudaMemcpy() 调用的任一侧记录事件。

如果你在这个设备上运行一个关于内存不足的错误[ZC9],你可能会得到一个更小的错误。实际上,到目前为止,我们的示例代码还没有费心检查运行时错误。在[VZX337]中,我们将学习如何在 CUDA C / C ++中执行错误处理以及如何查询当前设备以确定它们可用的资源,以便我们可以编写更健壮的代码。

关于作者

Mark Harris 是 NVIDIA 杰出的工程师,致力于 RAPIDS 。 Mark 拥有超过 20 年的 GPUs 软件开发经验,从图形和游戏到基于物理的模拟,到并行算法和高性能计算。当他还是北卡罗来纳大学的博士生时,他意识到了一种新生的趋势,并为此创造了一个名字: GPGPU (图形处理单元上的通用计算)。

审核编辑:郭婷

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

    关注

    28

    文章

    5099

    浏览量

    134447
  • API
    API
    +关注

    关注

    2

    文章

    2147

    浏览量

    66219
  • 计时器
    +关注

    关注

    1

    文章

    434

    浏览量

    34868
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    C/C++条件编译

    条件编译是一种在编译时根据条件选择性地包含或排除部分代码的处理方法。在 C/C++ ,条件编译使用预处理指令 #ifdef、#endif、#else 和 #elif 来实现。常用的条
    发表于 12-05 06:21

    C/C++代码静态测试工具Perforce QAC 2025.3的新特性

     Perforce Validate  QAC 项目的相对/根路径的支持。C++ 分析也得到了增强,增加了用于检测 C++ 并发问题的新检查,并改进了实体名称和实
    的头像 发表于 10-13 18:11 344次阅读
    <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>代码静态测试工具Perforce QAC 2025.3的新特性

    技能+1!如何在树莓派上使用C++控制GPIO?

    和PiGPIO等库,C++可用于编程控制树莓派的GPIO引脚。它提供了更好的性能和控制能力,非常适合对速度和精度要求较高的硬件项目。在树莓派社区,关于“Python
    的头像 发表于 08-06 15:33 3573次阅读
    技能+1!如<b class='flag-5'>何在</b>树莓派上使用<b class='flag-5'>C++</b>控制GPIO?

    请问如何在C++中使用NPU上的模型缓存?

    无法确定如何在 C++ 的 NPU 上使用模型缓存
    发表于 06-24 07:25

    主流的 MCU 开发语言为什么是 C 而不是 C++

    在单片机的地界儿里,C语言稳坐中军帐,C++想分杯羹?难喽。咱电子工程师天天跟那针尖大的内存空间较劲,C++那些花里胡哨的玩意儿,在这儿真玩不转。先说内存这道坎儿。您当stm32f4的256kRAM
    的头像 发表于 05-21 10:33 777次阅读
    主流的 MCU 开发语言为什么是 <b class='flag-5'>C</b> 而不是 <b class='flag-5'>C++</b>?

    何在 树莓派 上编写和运行 C 语言程序?

    在本教程,我将讨论C编程语言是什么,C编程的用途,以及如何在RaspberryPi上编写和运行C程序。本文的目的是为您介绍在Raspber
    的头像 发表于 03-25 09:28 936次阅读
    如<b class='flag-5'>何在</b> 树莓派 上编写和运行 <b class='flag-5'>C</b> 语言程序?

    C++学到什么程度可以找工作?

    C++学到什么程度可以找工作?要使用C++找到工作,特别是作为软件开发人员或相关职位,通常需要掌握以下几个方面: 1. **语言基础**:你需要对C++的核心概念有扎实的理解,包括但不限于指针、内存
    发表于 03-13 10:19

    创建了用于OpenVINO™推理的自定义C++和Python代码,从C++代码获得的结果与Python代码不同是为什么?

    创建了用于OpenVINO™推理的自定义 C++ 和 Python* 代码。 在两个推理过程中使用相同的图像和模型。 从 C++ 代码获得的结果与 Python* 代码不同。
    发表于 03-06 06:22

    源代码加密、源代码防泄漏c/c++与git服务器开发环境

    嵌入式开发企业中使用的c/c++开发语言,这类开发环境主要做电子信息行业比较多,员工通过c语言开发的程序,需要编译后,烧录到设备上去,开发环境及编译环境及其复杂,
    的头像 发表于 02-12 15:26 872次阅读
    源代码加密、源代码防泄漏<b class='flag-5'>c</b>/<b class='flag-5'>c++</b>与git服务器开发环境

    基于OpenHarmony标准系统的C++公共基础类库案例:ThreadPoll

    1、程序简介该程序是基于OpenHarmony标准系统的C++公共基础类库的线程池处理:ThreadPoll。本案例完成如下工作:创建1个线程池,设置该线程池内部有1024个线程空间。启动5个线程
    的头像 发表于 02-10 18:09 597次阅读
    基于OpenHarmony标准系统的<b class='flag-5'>C++</b>公共基础类库案例:ThreadPoll

    I2C总线在嵌入式系统的应用

    在现代电子设计,嵌入式系统扮演着越来越重要的角色。这些系统通常需要与多种外围设备进行通信,以实现数据的输入和输出。I2C总线作为一种多主机
    的头像 发表于 01-17 15:30 1166次阅读

    Spire.XLS for C++组件说明

    Spire.XLS for C++ 是一款专业的 C++ Excel 组件,可以用在各种 C++ 框架和应用程序。Spire.XLS for C+
    的头像 发表于 01-14 09:40 1295次阅读
    Spire.XLS for <b class='flag-5'>C++</b>组件说明

    EE-112:模拟C++的类实现

    电子发烧友网站提供《EE-112:模拟C++的类实现.pdf》资料免费下载
    发表于 01-03 15:15 0次下载
    EE-112:模拟<b class='flag-5'>C++</b><b class='flag-5'>中</b>的类<b class='flag-5'>实现</b>

    AKI跨语言调用库神助攻C/C++代码迁移至HarmonyOS NEXT

    )开发框架。它极大地简化了JS与C/C++之间的跨语言访问,为开发者提供了一种边界性编程体验友好的解决方案。通过AKI,开发者可以使用让代码更易读的语法糖,实现JS与C/
    发表于 01-02 17:08

    运动控制卡周期上报实时数据IO状态之C++

    使用C++进行运动控制卡的周期上报功能实现
    的头像 发表于 12-17 13:59 1526次阅读
    运动控制卡周期上报实时数据IO状态之<b class='flag-5'>C++</b>篇