GPU cudaMallocManaged 统一内存的优劣点

cudaMallocManaged 分配旨在供主机或设备代码使用的内存,并且现在仍在享受这种方法的便利之处,即在实现自动内存迁移且简化编程的同时,而无需深入了解 cudaMallocManaged 所分配统一内存 (UM) 实际工作原理的详细信息。nsys profile 提供有关加速应用程序中 UM 管理的详细信息,并在利用这些信息的同时结合对 UM 工作原理的更深入理解,进而为优化加速应用程序创造更多机会。

统一内存(UM)的迁移

分配 UM 时,内存尚未驻留在主机或设备上。主机或设备尝试访问内存时会发生 页错误,此时主机或设备会批量迁移所需的数据。同理,当 CPU 或加速系统中的任何 GPU 尝试访问尚未驻留在其上的内存时,会发生页错误并触发迁移。

能够执行页错误并按需迁移内存对于在加速应用程序中简化开发流程大有助益。此外,在处理展示稀疏访问模式的数据时(例如,在应用程序实际运行之前无法得知需要处理的数据时),以及在具有多个 GPU 的加速系统中,数据可能由多个 GPU 设备访问时,按需迁移内存将会带来显著优势。

有些情况下(例如,在运行时之前需要得知数据,以及需要大量连续的内存块时),我们还能有效规避页错误和按需数据迁移所产生的开销。

本实验的后续内容将侧重于对按需迁移的理解,以及如何在分析器输出中识别按需迁移。这些知识可让您在享受按需迁移优势的同时,减少其产生的开销。

统一内存(UM)的页错误

可以使用nsys 工具分析。

enerating CUDA Memory Operation Statistics...
CUDA Memory Operation Statistics (nanoseconds)

Time(%)      Total Time  Operations         Average         Minimum         Maximum  Name                                                                            
-------  --------------  ----------  --------------  --------------  --------------  --------------------------------------------------------------------------------
   78.8        42212544        2304         18321.4            2751          109728  [CUDA Unified Memory memcpy HtoD]                                               
   21.2        11349888         768         14778.5            1791           95136  [CUDA Unified Memory memcpy DtoH]                                               


# 数据迁移
CUDA Memory Operation Statistics (KiB)

              Total      Operations              Average            Minimum              Maximum  Name                                                                            
-------------------  --------------  -------------------  -----------------  -------------------  --------------------------------------------------------------------------------
         393216.000            2304              170.667              4.000             1020.000  [CUDA Unified Memory memcpy HtoD]                                               
         131072.000             768              170.667              4.000             1020.000  [CUDA Unified Memory memcpy DtoH]                                               


  • 当仅通过CPU访问统一内存时,是否存在内存迁移和/或页面错误的证据?
    没有。

  • 当仅通过GPU访问统一内存时,是否有证据表明内存迁移和/或页面错误?
    没有。

  • 当先由CPU然后由GPU访问统一内存时,是否有证据表明存在内存迁移和/或页面错误?
    有。

  • 当先由GPU然后由CPU访问统一内存时,是否存在内存迁移和/或页面错误的证据?
    有。

缺点

统一内存管理存在内存的迁移,在kernel执行的时候会降低kernel的执行效率。

优化:异步内存预取

在主机到设备和设备到主机的内存传输过程中,使用一种技术来减少页错误和按需内存迁移成本,此强大技术称为异步内存预取。通过此技术,程序员可以在应用程序代码使用统一内存 (UM) 之前,在后台将其异步迁移至系统中的任何 CPU 或 GPU 设备。此举可以减少页错误和按需数据迁移所带来的成本,并进而提高 GPU 核函数和 CPU 函数的性能。

此外,预取往往会以更大的数据块来迁移数据,因此其迁移次数要低于按需迁移。此技术非常适用于以下情况:在运行时之前已知数据访问需求且数据访问并未采用稀疏模式。

CUDA 可通过 cudaMemPrefetchAsync 函数,轻松将托管内存异步预取到 GPU 设备或 CPU。以下所示为如何使用该函数将数据预取到当前处于活动状态的 GPU 设备,然后再预取到 CPU:

int deviceId;
cudaGetDevice(&deviceId);                                         // The ID of the currently active GPU device.

cudaMemPrefetchAsync(pointerToSomeUMData, size, deviceId);        // Prefetch to GPU device.
cudaMemPrefetchAsync(pointerToSomeUMData, size, cudaCpuDeviceId); // Prefetch to host. `cudaCpuDeviceId` is a
                                                                  // built-in CUDA variable.

例子:

#include <stdio.h>

void initWith(float num, float *a, int N)
{
  for(int i = 0; i < N; ++i)
  {
    a[i] = num;
  }
}

__global__
void addVectorsInto(float *result, float *a, float *b, int N)
{
  int index = threadIdx.x + blockIdx.x * blockDim.x;
  int stride = blockDim.x * gridDim.x;

  for(int i = index; i < N; i += stride)
  {
    result[i] = a[i] + b[i];
  }
}

void checkElementsAre(float target, float *vector, int N)
{
  for(int i = 0; i < N; i++)
  {
    if(vector[i] != target)
    {
      printf("FAIL: vector[%d] - %0.0f does not equal %0.0f\n", i, vector[i], target);
      exit(1);
    }
  }
  printf("Success! All values calculated correctly.\n");
}

int main()
{
  int deviceId;
  int numberOfSMs;

  cudaGetDevice(&deviceId);
  cudaDeviceGetAttribute(&numberOfSMs, cudaDevAttrMultiProcessorCount, deviceId);
  printf("Device ID: %d\tNumber of SMs: %d\n", deviceId, numberOfSMs);

  const int N = 2<<24;
  size_t size = N * sizeof(float);

  float *a;
  float *b;
  float *c;

  cudaMallocManaged(&a, size);
  cudaMallocManaged(&b, size);
  cudaMallocManaged(&c, size);

  /*
   * Prefetching can also be used to prevent CPU page faults.
   */

//  将数据预取到CPU
  cudaMemPrefetchAsync(a, size, cudaCpuDeviceId);
  cudaMemPrefetchAsync(b, size, cudaCpuDeviceId);
  cudaMemPrefetchAsync(c, size, cudaCpuDeviceId);
  initWith(3, a, N);
  initWith(4, b, N);
  initWith(0, c, N);

//  将数据预取到GPU
  cudaMemPrefetchAsync(a, size, deviceId);
  cudaMemPrefetchAsync(b, size, deviceId);
  cudaMemPrefetchAsync(c, size, deviceId);

  size_t threadsPerBlock;
  size_t numberOfBlocks;

  threadsPerBlock = 256;
  numberOfBlocks = 32 * numberOfSMs;

  cudaError_t addVectorsErr;
  cudaError_t asyncErr;

  addVectorsInto<<<numberOfBlocks, threadsPerBlock>>>(c, a, b, N);

  addVectorsErr = cudaGetLastError();
  if(addVectorsErr != cudaSuccess) printf("Error: %s\n", cudaGetErrorString(addVectorsErr));

  asyncErr = cudaDeviceSynchronize();
  if(asyncErr != cudaSuccess) printf("Error: %s\n", cudaGetErrorString(asyncErr));

  /*
   * Prefetching can also be used to prevent CPU page faults.
   */

//  将数据预取到CPU
  cudaMemPrefetchAsync(c, size, cudaCpuDeviceId);
  checkElementsAre(7, c, N);

  cudaFree(a);
  cudaFree(b);
  cudaFree(c);
}


在使用异步预取进行了一系列重构之后,您应该看到内存传输次数减少了,但是每次传输的量增加了,并且内核执行时间大大减少了。

### 回答1: 在 Linux 系统中,可以通过在命令行中使用 NVIDIA 驱动程序提供的 nvidia-smi 工具来分配 GPU 内存。可以使用以下命令: ``` nvidia-smi --query-gpu=memory.total,memory.free,memory.used --format=csv ``` 此命令会显示 GPU内存,可用内存和已使用内存的数量。 还可以在执行 GPU 应用程序时指定要分配给它的内存量。例如,使用 CUDA 编程的 GPU 应用程序可以使用 CUDAcudaMallocManaged 函数分配托管内存,该内存将由 GPU 和 CPU 共享,并由系统自动管理。 ### 回答2: Linux系统中对于GPU内存的分配是通过驱动程序来管理的。具体的分配方式取决于所使用的显卡和驱动程序版本。 在Linux系统中,首先需要安装适当的显卡驱动程序,以便系统能够正确识别和连接到GPU。驱动程序通常会提供一些配置选项,可以通过修改相应的配置文件来分配GPU内存。 通常情况下,GPU内存的分配是动态的。这意味着当有需要时,系统会自动分配足够的GPU内存给正在运行的应用程序。这样可以确保应用程序能够充分利用GPU资源,而不会出现内存不足的情况。 一些显卡驱动程序也提供了手动配置选项,允许用户手动分配GPU内存。这些选项通常可以在配置文件中找到,并允许用户设置总的GPU内存大小以及每个应用程序可以使用的最大GPU内存。这种手动配置可以更精确地控制GPU内存的分配,但需要谨慎操作,以避免系统出现问题。 总之,Linux系统通过显卡驱动程序来管理GPU内存的分配,可以根据需要自动分配,也可以通过手动配置来进行分配。正确的GPU内存分配可以确保应用程序能够充分利用GPU资源,并提高系统的性能。 ### 回答3: 在Linux中,分配GPU内存主要通过显卡驱动程序来进行管理。常见的显卡驱动有NVIDIA的官方驱动和AMD的闭源驱动。 对于NVIDIA显卡,可以使用NVIDIA官方提供的NVIDIA驱动来管理GPU内存。在安装驱动后,可以通过命令行工具`nvidia-smi`来查看和管理GPU内存的分配情况。通过该工具,可以查看当前正在使用的GPU内存、空闲的GPU内存以及已经分配给进程的GPU内存等信息。 另外,NVIDIA也提供了CUDA工具包,其中包含了一些API,可以通过编程的方式来分配和管理GPU内存。通过CUDA,可以在代码中调用相关的API函数来申请和释放GPU内存。 对于AMD显卡,可以安装官方提供的闭源驱动,如AMDGPU-PRO。该驱动也提供了命令行工具`rocm-smi`来查看和管理GPU内存的使用情况。 与NVIDIA不同的是,对于AMD显卡,可以使用开源的ROCm(Radeon Open Compute)平台来进行GPU内存的分配和管理。ROCm提供了一系列的API函数,可以通过编程的方式来对GPU内存进行申请、释放和管理。 总结来说,Linux下的GPU内存分配主要通过显卡驱动程序来进行管理,常见的方式是使用对应的命令行工具或者通过编程调用相关的API函数来实现。无论是NVIDIA显卡还是AMD显卡,都提供了相应的工具和接口来满足GPU内存的分配需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝鲸123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值