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
    +关注

    关注

    27

    文章

    4400

    浏览量

    126541
  • 计时器
    +关注

    关注

    1

    文章

    395

    浏览量

    32139
收藏 人收藏

    评论

    相关推荐

    请问NFC数据传输如何保证数据安全?

    NFC数据传输如何保证数据安全
    发表于 04-07 06:18

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

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

    BF609的MCAPI中,如何利用mcapi中的scalar通道实现双核之间数据传输

    本人正使用bf609 的 EZ-Kit, 目前想利用mcapi中的scalar通道实现双核之间数据传输。 在core0和core1中已分别建立对应的endpoint,并能在core0中读取core1中对应通道,但却不能 连
    发表于 01-15 07:31

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

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

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

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

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

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

    如何实现MQTT协议数据传输

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

    AXI数据传输读写数据结构

    )等问题。 (1)窄位宽数据传输 当本次传输数据位宽小于通道本身的数据位宽时,称为窄位宽数据传输,或者直接翻译成窄
    的头像 发表于 10-31 16:17 614次阅读
    AXI<b class='flag-5'>数据传输</b>读写<b class='flag-5'>数据</b>结构

    嵌入式GPRS无线数据传输系统实现及应用

    电子发烧友网站提供《嵌入式GPRS无线数据传输系统实现及应用.pdf》资料免费下载
    发表于 10-26 11:11 0次下载
    嵌入式GPRS无线<b class='flag-5'>数据传输</b>系统<b class='flag-5'>实现</b>及应用

    基于W5300的以太网数据传输系统的设计与实现

    电子发烧友网站提供《基于W5300的以太网数据传输系统的设计与实现.pdf》资料免费下载
    发表于 10-24 09:45 0次下载
    基于W5300的以太网<b class='flag-5'>数据传输</b>系统的设计与<b class='flag-5'>实现</b>

    蓝牙POS机无线数据传输方案

    蓝牙BLE低功耗数据传输技术的使用加持在POS机新应用技术。 可以与移动终端智能设备,通过蓝牙配对功能,进行数据传输,通过移动终端显示电子小票,进行现场确认、签名,实现支付的功能。 也可以在 POS机和一系列设备(例如打印机、扫
    的头像 发表于 07-26 15:10 793次阅读
    蓝牙POS机无线<b class='flag-5'>数据传输</b>方案

    SPI数据传输有哪些方式

    SPI 数据传输可以有两种方式:同步方式和异步方式。 同步方式:数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到
    的头像 发表于 07-25 10:54 3256次阅读
    SPI<b class='flag-5'>数据传输</b>有哪些方式

    SPI中主机和从机模式的区别是什么?

    那么主机和从机模式到底有哪些区别呢? 弄懂SPI接口 SPI(Serial Peripheral Interface,串行外设接口)是Motorola公司提出的一种同步串行数据传输标准,在很多器件
    发表于 06-14 09:12

    DMA进行数据传输和CPU进行数据传输的疑问

    求大佬解答,本人正在学习STM32单片机中DMA直接数据存储部分的内容 看了DMA简介后,也上手过实例代码,但是没有实际的项目经验,所以有以下疑问: DMA外设在进行数据传输的操作,是否也是需要经过
    发表于 05-25 17:18

    如何测量ESP8266的数据传输速度?

    AP 的问候”。 现在我想测量数据从 AP 传输到客户端的数据传输速率/速度(不是波特率)。请注意,我使用的是 AT 命令来配置模块,我不是在谈论波特率。 有什么方法可以测量从一个模块到另一个模块的
    发表于 05-22 06:38