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

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

3天内不再提示

如何在主机和主机之间实现数据传输优化

星星科技指导员 来源:NVIDIA 作者:Mark Harris 2022-04-11 10:32 次阅读

主机和设备之间的传输是 GPU 计算中数据移动最慢的一个环节,所以您应该注意尽量减少传输。遵循这篇文章中的指导方针可以帮助你确保必要的转移是有效的。当您移植或编写新的 CUDA C / C ++代码时,我建议您从现有主机指针开始可分页的传输。正如我前面提到的,当您编写更多的设备代码时,您将消除一些中间传输,因此您在移植早期所花费的优化传输的任何努力都可能被浪费。另外,我建议您不要使用 CUDA 事件或其他计时器插入代码来测量每次传输所花费的时间,而是建议您使用 nvprof, 命令行 CUDA 探查器,或者使用可视化分析工具,如 NVIDIA 可视化探查器(也包括在 CUDA 工具箱中)。

这篇文章的重点是提高数据传输的效率。在 下一篇文章 中,我们讨论了如何将数据传输与计算和其他数据传输重叠。

在 C + C ++系列 之前的 帖子 中,我们为该系列的主要推力奠定了基础:如何优化 CUDA C / C ++代码。本文就如何在主机和主机之间高效地传输数据展开讨论。设备内存和 GPU 之间的峰值带宽远高于主机内存和设备内存之间的峰值带宽(例如,在 GPU NVIDIA C2050 上为 144 GB / s ),而在 PCIe x16 Gen2 上为 8 GB / s 。这种差异意味着主机和 GPU 设备之间的数据传输的实现可能会影响或破坏应用程序的整体性能。让我们从主机数据传输的一般原则开始。

尽可能减少主机和设备之间传输的数据量,即使这意味着在 GPU 上运行内核,与在主机 CPU 上运行内核相比,其速度几乎没有或几乎没有。

使用页锁定(或“固定”)内存时,主机和设备之间的带宽可能更高。

将许多小的传输批处理到一个较大的传输中执行得更好,因为它消除了每个传输的大部分开销。

主机和设备之间的数据传输有时可能与内核执行和其他数据传输重叠。

在这篇文章中,我们将研究上面的前三条准则,并在下一篇文章中专门讨论重叠数据传输。首先,我想谈谈如何在不修改源代码的情况下测量数据传输所花费的时间。

用 nvprof 测量数据传输时间

为了测量每次数据传输所花费的时间,我们可以在每次传输前后记录一个 CUDA 事件,并使用 cudaEventElapsedTime() ,正如我们所描述的 在上一篇文章中 , CUDA 工具箱中包含的命令行 CUDA 探查器(从 CUDA 5 开始)。让我们用下面的代码示例来尝试一下,您可以在 CUDA 中找到它。

int main() { const unsigned int N = 1048576; const unsigned int bytes = N * sizeof(int); int *h_a = (int*)malloc(bytes); int *d_a; cudaMalloc((int**)&d_a, bytes); memset(h_a, 0, bytes); cudaMemcpy(d_a, h_a, bytes, cudaMemcpyHostToDevice); cudaMemcpy(h_a, d_a, bytes, cudaMemcpyDeviceToHost); return 0; }

为了分析这段代码,我们只需使用nvcc编译它,然后用程序文件名作为参数运行nvprof

$ nvcc profile.cu -o profile_test $ nvprof ./profile_test

当我在台式电脑上运行时,它有一个 geforcegtx680 ( GK104GPU ,类似于 Tesla K10 ),我得到以下输出。

$ nvprof ./a.out ======== NVPROF is profiling a.out... ======== Command: a.out ======== Profiling result: Time(%) Time Calls Avg Min Max Name 50.08 718.11us 1 718.11us 718.11us 718.11us [CUDA memcpy DtoH] 49.92 715.94us 1 715.94us 715.94us 715.94us [CUDA memcpy HtoD]

如您所见, nvprof 测量每个 CUDA memcpy 调用所花费的时间。它报告每个调用的平均、最小和最长时间(因为我们只运行每个副本一次,所有时间都是相同的)。 nvprof 非常灵活,所以请确保 查看文档 。

nvprof 是 CUDA 5 中的新功能。如果您使用的是早期版本的 CUDA ,那么可以使用旧的“命令行分析器”,正如 Greg Ruetsch 在他的文章 如何在 CUDA Fortran 中优化数据传输 中所解释的那样。

最小化数据传输

我们不应该只使用内核的 GPU 执行时间相对于其 CPU 实现的执行时间来决定是运行 GPU 还是 CPU 版本。我们还需要考虑在 PCI-e 总线上移动数据的成本,尤其是当我们最初将代码移植到 CUDA 时。因为 CUDA 的异构编程模型同时使用了 CPU 和 GPU ,代码可以一次移植到 CUDA 一个内核。在移植的初始阶段,数据传输可能支配整个执行时间。将数据传输所花费的时间与内核执行的时间分开记录是值得的。正如我们已经演示过的,使用命令行探查器很容易做到这一点。随着我们移植更多的代码,我们将删除中间传输并相应地减少总体执行时间。

固定主机内存

默认情况下,主机( CPU )的数据分配是可分页的。 GPU 无法直接从可分页主机内存访问数据,因此当调用从可分页主机内存到设备内存的数据传输时, CUDA 驱动程序必须首先分配一个临时页锁定或“固定”主机数组,将主机数据复制到固定数组,然后将数据从固定数组传输到设备内存,如下图所示。

如图中所示,固定内存用作从设备到主机的传输的临时区域。通过直接将主机数组分配到固定内存中,可以避免在可分页主机数组和固定主机数组之间进行传输的开销。使用 CUDA 或 cudaHostAlloc() 在 CUDA C / C ++中分配被锁定的主机内存,并用 cudaFreeHost() 解除它。固定内存分配可能会失败,因此应该始终检查错误。下面的代码摘要演示如何分配固定内存以及错误检查。

cudaError_t status = cudaMallocHost((void**)&h_aPinned, bytes); if (status != cudaSuccess) printf("Error allocating pinned host memory
");

使用主机固定内存的数据传输使用与可分页内存传输相同的cudaMemcpy()语法。我们可以使用下面的“带宽测试”程序(Github 上也有)来比较可分页和固定的传输速率。

#include 
#include 

// Convenience function for checking CUDA runtime API results
// can be wrapped around any runtime API call. No-op in release builds.
inline
cudaError_t checkCuda(cudaError_t result)
{
#if defined(DEBUG) || defined(_DEBUG)
  if (result != cudaSuccess) {
    fprintf(stderr, "CUDA Runtime Error: %s\n",
            cudaGetErrorString(result));
    assert(result == cudaSuccess);
  }
#endif
  return result;
}

void profileCopies(float        *h_a,
                   float        *h_b,
                   float        *d,
                   unsigned int  n,
                   char         *desc)
{
  printf("\n%s transfers\n", desc);

  unsigned int bytes = n * sizeof(float);

  // events for timing
  cudaEvent_t startEvent, stopEvent;

  checkCuda( cudaEventCreate(&startEvent) );
  checkCuda( cudaEventCreate(&stopEvent) );

  checkCuda( cudaEventRecord(startEvent, 0) );
  checkCuda( cudaMemcpy(d, h_a, bytes, cudaMemcpyHostToDevice) );
  checkCuda( cudaEventRecord(stopEvent, 0) );
  checkCuda( cudaEventSynchronize(stopEvent) );

  float time;
  checkCuda( cudaEventElapsedTime(&time, startEvent, stopEvent) );
  printf("  Host to Device bandwidth (GB/s): %f\n", bytes * 1e-6 / time);

  checkCuda( cudaEventRecord(startEvent, 0) );
  checkCuda( cudaMemcpy(h_b, d, bytes, cudaMemcpyDeviceToHost) );
  checkCuda( cudaEventRecord(stopEvent, 0) );
  checkCuda( cudaEventSynchronize(stopEvent) );

  checkCuda( cudaEventElapsedTime(&time, startEvent, stopEvent) );
  printf("  Device to Host bandwidth (GB/s): %f\n", bytes * 1e-6 / time);

  for (int i = 0; i < n; ++i) {
    if (h_a[i] != h_b[i]) {
      printf("*** %s transfers failed ***\n", desc);
      break;
    }
  }

  // clean up events
  checkCuda( cudaEventDestroy(startEvent) );
  checkCuda( cudaEventDestroy(stopEvent) );
}

int main()
{
  unsigned int nElements = 4*1024*1024;
  const unsigned int bytes = nElements * sizeof(float);

  // host arrays
  float *h_aPageable, *h_bPageable;
  float *h_aPinned, *h_bPinned;

  // device array
  float *d_a;

  // allocate and initialize
  h_aPageable = (float*)malloc(bytes);                    // host pageable
  h_bPageable = (float*)malloc(bytes);                    // host pageable
  checkCuda( cudaMallocHost((void**)&h_aPinned, bytes) ); // host pinned
  checkCuda( cudaMallocHost((void**)&h_bPinned, bytes) ); // host pinned
  checkCuda( cudaMalloc((void**)&d_a, bytes) );           // device

  for (int i = 0; i < nElements; ++i) h_aPageable[i] = i;
  memcpy(h_aPinned, h_aPageable, bytes);
  memset(h_bPageable, 0, bytes);
  memset(h_bPinned, 0, bytes);

  // output device info and transfer size
  cudaDeviceProp prop;
  checkCuda( cudaGetDeviceProperties(&prop, 0) );

  printf("\nDevice: %s\n", prop.name);
  printf("Transfer size (MB): %d\n", bytes / (1024 * 1024));

  // perform copies and report bandwidth
  profileCopies(h_aPageable, h_bPageable, d_a, nElements, "Pageable");
  profileCopies(h_aPinned, h_bPinned, d_a, nElements, "Pinned");

  printf("n");

  // cleanup
  cudaFree(d_a);
  cudaFreeHost(h_aPinned);
  cudaFreeHost(h_bPinned);
  free(h_aPageable);
  free(h_bPageable);

  return 0;
}

数据传输速率取决于主机系统的类型(主板, CPU 和芯片组)以及 GPU 。在我的笔记本电脑上,它有 Intel Core i7-2620MCPU ( 2 . 7GHz , 2 个 Sandy Bridge 内核, 4MB L3 缓存)和 NVIDIA NVS 4200MGPU ( 1 费米 SM ,计算能力 2 . 1 , PCI-e Gen2 x16 ),运行BandwidthTest会产生以下结果。如您所见,固定传输的速度是可分页传输的两倍多。

Device: NVS 4200M Transfer size (MB): 16 Pageable transfers Host to Device bandwidth (GB/s): 2.308439 Device to Host bandwidth (GB/s): 2.316220 Pinned transfers Host to Device bandwidth (GB/s): 5.774224 Device to Host bandwidth (GB/s): 5.958834

更快速的 3GHz 处理器( 3GHz , 3GHz )和 3K 处理器( 3GHz )相比,我们可以更快地使用 3K 处理器( 3GHz )和 3GHz 处理器。这大概是因为更快的 CPU (和芯片组)降低了主机端的内存复制成本。

Device: GeForce GTX 680 Transfer size (MB): 16 Pageable transfers Host to Device bandwidth (GB/s): 5.368503 Device to Host bandwidth (GB/s): 5.627219 Pinned transfers Host to Device bandwidth (GB/s): 6.186581 Device to Host bandwidth (GB/s): 6.670246

不应过度分配固定内存。这样做会降低整体系统性能,因为这会减少操作系统和其他程序可用的物理内存量。多少是太多是很难预先判断的,所以对于所有优化,测试您的应用程序和它们运行的系统,以获得最佳性能参数。

批量小转移

由于与每个传输相关联的开销,最好将多个小传输一起批处理到单个传输中。通过使用一个临时数组(最好是固定的)并将其与要传输的数据打包,这很容易做到。

对于二维数组传输,可以使用 cudaMemcpy2D() 。

cudaMemcpy2D(dest, dest_pitch, src, src_pitch, w, h, cudaMemcpyHostToDevice)

这里的参数是指向第一个目标元素和目标数组间距的指针,指向第一个源元素和源数组间距的指针,要传输的子矩阵的宽度和高度,以及 memcpy 类型。还有一个 cudaMemcpy3D() 函数用于传输秩为三的数组部分。

关于作者

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

审核编辑:郭婷

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

    关注

    28

    文章

    4661

    浏览量

    128526
  • 计时器
    +关注

    关注

    1

    文章

    416

    浏览量

    32591
收藏 人收藏

    评论

    相关推荐

    LORA模块的数据传输速率

    LoRa(Long Range)是一种用于物联网(IoT)应用的低功耗广域网(LPWAN)技术。它以其长距离通信能力和低功耗特性而闻名。LoRa模块的数据传输速率可以根据不同的配置和地区的规定
    的头像 发表于 10-31 17:03 226次阅读

    海外HTTP服务优化:提升国际数据传输效率

    针对海外HTTP服务优化以提升国际数据传输效率的问题。
    的头像 发表于 10-17 07:18 139次阅读

    网络数据传输速率的单位是什么

    网络数据传输速率的单位是 bps(bit per second) ,即比特每秒,也可以表示为b/s或bit/s。它表示的是每秒钟传输的二进制数的位数。比特(bit)是计算机中数据量的单位,也是信息论
    的头像 发表于 10-12 10:20 673次阅读

    usb主机模式和设备模式的区别

    : USB主机模式 在USB主机模式下,计算机或其他设备扮演主机的角色,负责管理USB总线上的通信。主机控制数据流,包括初始化连接、
    的头像 发表于 09-25 09:06 391次阅读

    SD NAND应用存储功能描述(5)数据传输

    数据传输方式 : 在卡识别模式结束之前,主机应保持在最高频率,因为某些卡在卡识别模式期间可能有工作频率限制。在数据传输模式下,主机可以在fpp频率范围内操作卡。
    的头像 发表于 08-05 10:23 227次阅读
    SD NAND应用存储功能描述(5)<b class='flag-5'>数据传输</b>

    二总线——MCU有线数据传输

    交互直接透传协议的,但可以实现二根线上完成数据传输+供电,尤其适合楼宇里的智能设备联动控制,如烟雾报警器与报警主机之间,如智能灯光控制等等一切合理的应用。 POWERBUS二总线技术,
    发表于 07-23 13:28

    探索SPI单线传输模式中时钟线与数据传输的简化

    通信的简化需求也日益增加。在这种背景下,SPI的单线传输模式成为了一个备受关注的解决方案。 SPI协议概述 SPI协议是一种常用的同步串行通信协议,通常用于微控制器与其他设备之间数据传输。它基于主从架构,允许一个
    的头像 发表于 05-28 18:26 1033次阅读

    GMSL技术 实现高带宽、低延迟和高可靠性数据传输# ADI# GMSL# 汽车# 数据传输

    adi数据传输电机
    Excelpoint世健
    发布于 :2024年05月17日 16:34:25

    DTU的多种协议,解锁数据传输的无限可能

    DTU,即数据传输单元,是一种在物联网(IoT)网络中常用的设备,主要用于在传感器和智能设备之间进行数据传输。DTU使用多种协议来实现这一目标,这些协议不仅提高了
    的头像 发表于 03-01 11:00 685次阅读
    DTU的多种协议,解锁<b class='flag-5'>数据传输</b>的无限可能

    数字会议控制主机的定义和功能

    数字会议控制主机是一种先进的会议设备,主要用于实现会议的数字化和智能化。它集成了多种功能,包括音频处理、视频传输数据传输、智能协作等,为会议参与者提供全方位的服务。 数字会议控制
    的头像 发表于 01-23 14:34 879次阅读

    什么是讯维数字会议控制主机

    讯维数字会议控制主机是一种专为音视频工程应用而研发的现代化高科技音视频信号处理设备。它集成了多种功能,包括音频处理、视频传输数据传输、智能协作等,为会议参与者提供全方位的服务。   讯维数字会议
    的头像 发表于 01-23 14:28 345次阅读

    stm32 usb 主机发送 pid in的原理和实现方法

    中,我们将深入探讨STM32 USB主机发送PID IN的原理和实现方法。 首先,让我们来了解一下USB协议中的PID(Packet Identifier)。PID是USB数据传输的核心部分,它用于标识
    的头像 发表于 12-20 15:56 1207次阅读

    手机没有OTG功能,如何实现数据传输

    手机没有OTG功能,如何实现数据传输? 手机没有OTG功能,需要传输数据的时候可以考虑以下几种方法: 1. 云端存储 云端存储是目前非常流行的一种
    的头像 发表于 12-11 15:31 3166次阅读

    虹科方案 | 如何破解CAN与车载以太网之间数据传输和协议转换的难题?

    在车辆网络时代,数据传输和协议转换在通信领域中扮演着至关重要的角色。它们不仅能够实现车辆内部系统之间的互联互通,还支持车辆与外部网络进行通信,从而为驾驶者带来更智能、便捷的驾驶体验。本文将介绍CAN总线与车载以太网协议在通讯中的
    的头像 发表于 11-27 09:55 856次阅读
    虹科方案 | 如何破解CAN与车载以太网<b class='flag-5'>之间数据传输</b>和协议转换的难题?

    如何实现MQTT协议数据传输

    如何实现MQTT协议数据传输? 随着物联网技术的不断发展,越来越多的设备和应用需要实现互联互通。而MQTT作为一种轻量级的发布/订阅消息传输协议,在物联网领域应用广泛,成为了许多设备
    的头像 发表于 11-15 17:23 1073次阅读