0. 前言

📣按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解及成果,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。

CUDA(Compute Unified Device Architecture)作为NVIDIA推出的通用并行计算架构,已成为GPU加速计算的事实标准。在CUDA编程中,高效的内存管理是获得最佳性能的关键因素之一。本文将全面介绍CUDA内存模型、各类内存管理函数的使用方法,并通过实际示例展示如何优化内存访问模式。

1. CUDA内存模型概述

1.1 内存层次结构

CUDA设备具有复杂的内存层次结构,主要包括:

  • 全局内存(Global Memory):容量最大(通常数GB),所有线程可访问,但延迟高(400-800周期)
  • 共享内存(Shared Memory):片上内存,速度比全局内存快100倍,但容量有限(每SM通常16-64KB)
  • 寄存器(Registers):最快的存储,每个线程私有
  • 常量内存(Constant Memory):只读缓存,适合广播数据
  • 纹理内存(Texture Memory):专为图形处理优化的特殊缓存

在这里插入图片描述

1.2 内存访问特性比较

内存类型物理位置作用域生命周期带宽/速度使用场景显式控制关键字
寄存器GPU芯片寄存器文件线程私有线程生命周期最高(1周期)高频访问的私有变量(如循环计数器)自动分配(局部变量)
共享内存GPU芯片上的SM线程块共享线程块生命周期高(1-32周期)线程协作(如归约运算、矩阵分块)__shared__
本地内存实际在全局内存中线程私有线程生命周期中低(≈全局内存)大数组或寄存器不足时的溢出变量编译器自动分配
全局内存GPU设备显存所有线程+主机应用生命周期中(400-800周期)大规模数据存储,需频繁访问时需合并访问优化cudaMalloc分配
常量内存GPU芯片上的缓存所有线程只读应用生命周期中(缓存加速)需要广播给所有线程的只读数据(如配置参数)__constant__
纹理内存GPU专用缓存所有线程应用生命周期中(优化访存)图形处理、具有空间局部性的非对齐访问纹理API绑定
主机内存CPU内存主机+设备(需拷贝)应用生命周期最低(PCIe瓶颈)CPU-GPU数据传输的中间存储malloc/cudaHostAlloc

下面我将详细地介绍CUDA中各种内存管理函数的功能、参数和使用方法,。

2. 设备内存分配函数cudaMalloc

2.1 cudaMalloc的基本使用方法

功能:在设备上分配线性内存。

函数原型

cudaError_t cudaMalloc(void** devPtr, size_t size);

参数详解

  • devPtr:指向设备指针的指针,分配的内存地址将存储在这里
  • size:要分配的字节数

返回值

  • 返回cudaSuccess表示成功,其他值表示错误

注意事项

  • 分配的内存不会被初始化
  • 分配的内存是设备全局内存
  • 必须使用cudaFree释放

示例

#include <stdio.h>
#include <cuda_runtime.h>

int main() {
	float* d_data;
	size_t bytes = 1024 * sizeof(int);
	cudaError_t err = cudaMalloc(&d_data, bytes);
	printf("%s\n", cudaGetErrorString(err));
	cudaFree(d_data);
	return 0;
}

输出为:

在这里插入图片描述

2.2 cudaMalloc内存关系详解

在CUDA中,cudaMalloc(void** devPtr, size_t size)用于在GPU设备上动态分配内存,其内存关系分为三层:

接下来会有点绕,要看懂下面这个图

在这里插入图片描述

  1. 主机层

    • devPtrcudaMalloc 的参数(即 &d_data),类型为 void**,用于定位 d_data 的内存位置(如0x7ffd1234)。
    • d_data 是主机上的指针变量(如 float* d_data)。
  2. 设备层

    • cudaMalloc 在 GPU 内存中分配空间(如size=1024),返回设备地址(如 0xA000)。
  3. 关联层

    • cudaMalloc 通过解引用 *devPtr(即 d_data),将设备地址写入主机变量 d_data
    • 最终:
      • d_data(主机变量)的值变为 0xA000,指向 GPU 内存。
      • GPU 内存块(地址 0xA000)存储实际数据。

3. cudaMallocmalloc对比

CUDA中的cudaMalloc 和标准C中的 malloc 都是内存分配函数,但它们在设计和使用上有本质区别,主要体现在以下方面:

3.1 内存类型不同

  • malloc

    • 分配的是 主机内存(Host Memory),由 CPU 直接管理
    • 例如:int* h_data = (int*)malloc(N * sizeof(int));
  • cudaMalloc

    • 分配的是 设备内存(Device Memory),位于 GPU 上,CPU 不能直接访问
    • 例如:int* d_data; cudaMalloc(&d_data, N * sizeof(int));

3.2 参数传递方式不同

  • malloc

    • 直接返回分配的内存地址(void* 类型)
    • 示例:
      void* ptr = malloc(size);
      
  • cudaMalloc

    • CUDA 需要返回错误码(cudaError_t),无法像 malloc 那样直接返回指针
    • 示例:
      int* d_data;
      cudaError_t err = cudaMalloc(&d_data, size); // 传入指针的地址
      

3.3 错误处理方式不同

  • malloc

    • 失败时返回 NULL,需要手动检查:
      int* h_data = (int*)malloc(size);
      if (h_data == NULL) {
          printf("malloc failed!\n");
      }
      
  • cudaMalloc

    • 返回 cudaError_t 错误码,需用 cudaGetErrorString 检查:
      cudaError_t err = cudaMalloc(&d_data, size);
      if (err != cudaSuccess) {
          printf("cudaMalloc failed: %s\n", cudaGetErrorString(err));
      }
      

3.4 代码对比示例

// 主机内存分配(CPU)
int* h_data = (int*)malloc(N * sizeof(int));
if (h_data == NULL) {
    printf("Host malloc failed!\n");
    return -1;
}
free(h_data);
// 设备内存分配(GPU)
int* d_data;
cudaError_t err = cudaMalloc(&d_data, N * sizeof(int));
if (err != cudaSuccess) {
    printf("cudaMalloc failed: %s\n", cudaGetErrorString(err));
    return -1;
}
cudaFree(d_data);

对比总结

特性malloccudaMalloc
内存类型主机内存(CPU)设备内存(GPU)
参数形式void* malloc(size)cudaError_t cudaMalloc(void**)
错误处理返回 NULL返回 cudaError_t
释放方式free()cudaFree()
访问权限CPU 可直接访问必须通过 cudaMemcpy 传输

核心区别
malloc 用于 CPU 内存管理,而 cudaMalloc 专为 GPU 内存设计,必须配合 CUDA API 使用。

4. 设备内存释放函数cudaFree

功能:释放设备上分配的内存。

函数原型

cudaError_t cudaFree(void* devPtr);

参数详解

  • devPtr:要释放的设备内存指针

返回值

  • 返回no error表示成功

注意事项

  • 不能释放未分配或已释放的内存
  • 释放后指针变为无效,不应再使用

示例

#include <stdio.h>
#include <cuda_runtime.h>

int main() {
	float* d_data;
	size_t bytes = 1024 * sizeof(int);
	cudaMalloc(&d_data, bytes);
	cudaError_t err = cudaFree(d_data);
	printf("%s", cudaGetErrorString(err));
	return 0;
}

输出为:

在这里插入图片描述

5. 结语与展望

本文深入探讨了CUDA内存模型的核心概念,重点剖析了设备内存分配函数cudaMalloc的实现机制及其与标准malloc的关键差异。通过内存层次图示和代码示例,我们清晰地展示了主机指针与设备内存的关联方式,这种"指针的指针"设计模式充分体现了CUDA对异构计算架构的深刻考量。cudaFree的配套使用则完善了设备内存的生命周期管理闭环。

掌握cudaMalloc仅仅是CUDA高性能编程的起点。在实际应用中,我们还需要理解各类内存传输函数(如cudaMemcpy),在后续文章中我们将逐步说明。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐