首页 > 系统相关 >CUDA统一内存知识点总结

CUDA统一内存知识点总结

时间:2024-01-06 23:32:03浏览次数:37  
标签:知识点 int 数据 CUDA GPU data CPU 内存

背景及概念

在典型的个人计算机或集群节点中,CPU和GPU的内存物理上是分离的,通过PCI-Express总线连接。在CUDA 6之前,程序员必须将共享数据分配到两个不同的内存中,并显式复制,给CUDA程序带来了繁琐的复杂性。 CUDA6.0于2014年发布,Unified Memory(UM) 就是本次发布引入的。

UM允许开发者在编写CUDA程序时使用一致的内存地址空间,而不需要手动管理CPU和GPU之间的数据传输。这一特性在很大程度上简化了GPU编程,并提高了开发效率。 UM 的引入使得数据在CPU和GPU之间的传输更为透明,由运行时系统负责在需要时自动进行数据迁移。

统一内存创建了一个在 CPU 和 GPU 之间共享的托管内存池,弥合了 CPU-GPU 鸿沟。 CPU 和 GPU 都可以使用cudaMallocManaged分配的指针访问托管内存(即相同的数据),无需为不同的设备维护不同的指针。 关键是系统会根据需要动态地自动在CPU和GPU之间迁移统一内存中分配的数据,以确保数据在使用时位于合适的设备上。

简单应用-分配UM

cudaMallocManaged 是用于在CUDA程序中分配Unified Memory的函数。 这个函数会返回一个指针,可以在CPU和GPU上使用, 而且内存是在共享的虚拟地址空间中分配的。

函数定义

<code>cudaError_t cudaMallocManaged(void** devPtr, size_t size, unsigned int flags = cudaMemAttachGlobal);</code>

  1. devPtr:一个指向指针的指针,用于存储分配的内存块的起始地址。在这个指针中,你可以在 CPU 和 GPU 上访问分配的内存。
  2. size:要分配的内存块的字节数。
  3. flags:附加标志,用于指定内存的使用方式。在大多数情况下,使用默认值 cudaMemAttachGlobal 即可。
  4. 返回值:返回值是一个 cudaError_t 类型的错误码,用于指示函数是否成功执行。如果返回值是 cudaSuccess,则说明分配成功。

函数的简单使用 以下是一个简单的使用cudaMallocManaged 分配 Unified Memory的样例:

#include <iostream>

__global__ void kernel(int *data) {
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    data[tid] += 100; // 在GPU上修改Unified Memory的数据
}

int main() {
    const int N = 10;
    int *data;

    // 使用 cudaMallocManaged 分配 Unified Memory
    cudaMallocManaged(&data, N * sizeof(int));

    // 在 CPU 上访问和修改数据
    for (int i = 0; i < N; ++i) {
        data[i] = i;
    }

    // 在 GPU 上调用核函数,修改数据
    // 请注意,由于 UM,data 指针可以在 CPU 和 GPU 上使用
    // 所以不需要显式的数据传输
    kernel<<<1, N>>>(data);
    cudaDeviceSynchronize(); // 等待 GPU 执行完成

    // 再次在 CPU 上访问数据
    for (int i = 0; i < N; ++i) {
        std::cout << data[i] << " ";
    }

    // 释放分配的 Unified Memory
    cudaFree(data);

    return 0;
}

UM的数据迁移

自动数据迁移:当 CPU 试图访问 GPU 分配的 UM 内存时,或者当 GPU 试图访问 CPU 分配的 UM 内存时,CUDA 运行时系统会自动触发数据的迁移。

延迟和异步操作:

数据迁移可能引入一定的延迟,因为系统需要判断何时以及如何进行迁移。在某些情况下,这可能导致性能下降。 为了优化性能,可以使用异步操作,如异步内存迁移和异步执行核函数。异步操作允许 CPU 或 GPU 在等待数据迁移的同时执行其他操作,提高整体并行性 异步内存迁移:

使用 CUDA 流(stream)可以实现异步内存迁移。CUDA 流是一种在 GPU 上并发执行操作的机制 每个流都代表了一条独立的指令序列,可以在 GPU 上并发执行。 通过将内存迁移与核函数执行异步化,可以减小对性能的影响。

#include <iostream>

__global__ void kernel(int* data, int val) {
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    data[tid] += val;
}

int main() {
    const int N = 256;
    int *data;

    // 使用 cudaMallocManaged 分配 Unified Memory
    cudaMallocManaged(&data, N * sizeof(int));

    // 在 CPU 上访问和初始化数据
    for (int i = 0; i < N; ++i) {
        data[i] = i;
    }

    // 在 GPU 上调用核函数,异步执行
    cudaStream_t stream;
    cudaStreamCreate(&stream);

    kernel<<<1, N, 0, stream>>>(data, 10);

    // 在流上启动异步内存迁移
    cudaMemcpyAsync(data + N/2, data, N/2 * sizeof(int), cudaMemcpyDeviceToDevice, stream);

    // 在流上启动异步核函数执行
    kernel<<<1, N/2, 0, stream>>>(data + N/2, 20);

    cudaStreamSynchronize(stream); // 等待流执行完成

    // 在 CPU 上访问修改后的数据
    for (int i = 0; i < N; ++i) {
        std::cout << data[i] << " ";
    }

    // 销毁流
    cudaStreamDestroy(stream);

    // 释放分配的 Unified Memory
    cudaFree(data);

    return 0;
}

数据迁移的方向:

数据可以从 CPU 到 GPU 迁移,也可以从 GPU 到 CPU 迁移。

内存访问亲和性

UM 允许指定内存的访问亲和性,即数据在被特定设备首次访问时将会被分配到该设备的内存。

这可以通过 cudaMemPrefetchAsync 来实现。

<code>cudaError_t cudaMemPrefetchAsync(const void* devPtr, size_t count, int dstDevice, cudaStream_t stream = 0);</code> devPtr:指向要迁移的内存块的指针。 count:要迁移的字节数。 dstDevice:目标设备的设备 ID。 stream:CUDA 流,表示异步执行的流。默认为0,表示默认流。

//示例代码:
#include <iostream>

int main() {
    const int N = 10;
    int *data;

    // 使用 cudaMallocManaged 分配 Unified Memory
    cudaMallocManaged(&data, N * sizeof(int));

    // 将数据迁移到 GPU 0 上的内存
    cudaMemPrefetchAsync(data, N * sizeof(int), 0);

    // 在 CPU 上访问和修改数据
    for (int i = 0; i < N; ++i) {
        data[i] = i;
    }

    // 在 GPU 0 上调用核函数,修改数据
    // 请注意,由于 UM,data 指针可以在 CPU 和 GPU 上使用
    // 所以不需要显式的数据传输
    kernel<<<1, N>>>(data);
    cudaDeviceSynchronize(); // 等待 GPU 执行完成

    // 释放分配的 Unified Memory
    cudaFree(data);

    return 0;
}

在这个示例中,cudaMemPrefetchAsync 函数将数据 data 预取到 GPU 0 上的内存,以提高对该设备的访问性能。

共享内存与常量内存

共享内存: 共享内存是一种在线程块内部共享的内存,它可以被同一线程块中的所有线程访问。 通过 shared 关键字定义共享内存,它在 GPU 上的每个线程块中都有一份拷贝。 共享内存的使用可以显著提高访问速度,因为它位于 GPU 的多个线程之间的共享存储器中。 常量内存: 常量内存是一种只读内存,对所有线程块和线程都是全局唯一的。它对于那些在执行期间不变的数据非常有用。 常量内存通常用于存储在内核函数执行期间不变的常量数据。 内存共享策略: 在 UM 中,内存的共享策略可以通过内存的作用域和生命周期来决定。 全局内存是 UM 中的默认内存类型,所有设备都能访问,生命周期取决于分配和释放的时间点。 通过共享内存,你可以在线程块中共享数据,提高访问速度。 常量内存适用于在内核函数执行期间不变的常量数据。

#include <iostream>

__global__ void kernel(int* data) {
    // 在共享内存中分配数组,每个线程块有一份拷贝
    __shared__ int sharedData[256];

    // 在常量内存中定义常量
    __constant__ int constantValue = 42;

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

    // 使用共享内存
    sharedData[threadIdx.x] = data[tid];
    __syncthreads(); // 确保所有线程都已经写入共享内存

    // 使用常量内存
    int result = sharedData[threadIdx.x] + constantValue;
    data[tid] = result;
}

int main() {
    const int N = 256;
    int *data;

    // 使用 cudaMallocManaged 分配 Unified Memory
    cudaMallocManaged(&data, N * sizeof(int));

    // 在 CPU 上访问和初始化数据
    for (int i = 0; i < N; ++i) {
        data[i] = i;
    }

    // 在 GPU 上调用核函数,修改数据
    kernel<<<1, N>>>(data);
    cudaDeviceSynchronize(); // 等待 GPU 执行完成

    // 在 CPU 上访问修改后的数据
    for (int i = 0; i < N; ++i) {
        std::cout << data[i] << " ";
    }

    // 释放分配的 Unified Memory
    cudaFree(data);

    return 0;
}

共享内存在同一线程块中的所有线程之间共享

常量内存是全局唯一的,适用于不变的常量数据。

标签:知识点,int,数据,CUDA,GPU,data,CPU,内存
From: https://blog.51cto.com/u_14882565/9128323

相关文章

  • 动态内存管理:malloc free——《初学C语言第50天》
    //////——————1.动态内存管理(内存空间)////共四个函数:malloc free calloc realloc////1.为什么存在动态内存分配////我们已经掌握的内存开辟方式有:////intval=20;//在栈空间上开辟四个字节////chararr[10]={0};//在栈空间上开辟10个字节的连续空间////......
  • 多线程(互斥锁,条件变量,虚假唤醒)知识点总结
    互斥锁mutexC++11一共提出四种互斥锁std::mutex:独占的互斥锁,不能递归使用std::timed_mutex:带超时的独占互斥锁,不能递归使用std::recursive_mutex:递归互斥锁,不带超时功能std::recursive_timed_mutex:带超时的递归互斥锁1.mutexmutex有三个成员函数:voidlock();booltry_loc......
  • 用RWEverything刷内存spd
    最近参与的一个项目,由于主板只支撑ddr3L的内存,需要把ddr3标压内存刷为ddr3L。通过百度找到如下:不无折腾篇五:DDR3标压内存改DDR3L低压条保姆级傻瓜教程于是动手实践了一下,特此记录本过程:1、找台支持ddr3标压的机子作为刷机平台,并下载好软件RWEverything1.72、寻找能刷spd的内......
  • 浪潮服务器某根内存容量减半-32G变成16G
    服务器某根内存条内存容量减半;内存配置:32G*16根问题:CPU0_C2D0槽位内存显示为16G型号:三星32GDDR43200MHz 停机更换后恢复正常......
  • Linux核心组件(CPU,内存,磁盘,网络,进程)
    一、CPU不同组件,每秒的运算、读写次数命令集:lscpu+top/htop/uptime+mpstat+sar#查看CPU静态信息cat/proc/cpuinfo#查看CPU统计信息lscpu uptimeLoadAverage(平均负载):就是一段时间内(1分钟、5分钟、15分钟)内平均Load翻译:一段时间内正在使用和等待使用CPU的平均进程......
  • 全何发布96GB大容量DDR5内存:8400MHz超高频率
    1月5日消息,台湾全何(V-Color)发布了全新的MantaXFinity系列DDR5内存,不但拥有最高96GB大容量,还有8400MHz最高频率。新内存提供2x16GB、2×24GB、2×32GB、2×48GB等不同容量套装,频率最高可选8400MHz。专门针对Intel平台优化,并支持XMP3.0,可以一键达成高频。散热马甲也重新设计......
  • 【Redis深度专题】「核心技术提升」从源码角度探究Redis服务的内存使用、清理以及逐出
    背景介绍Redis作为一种高性能的内存NoSQL数据库,其容量受限于最大内存的限制。用户在使用阿里云Redis时,除了对性能和稳定性有较高的要求外,对内存占用也非常敏感。然而,在实际使用中,一些用户可能会发现他们的线上实例的内存占用比预期的要大。内存较高的场景在使用Redis时,以下是一些可......
  • 异构编程模型知识点总结
    如何理解“异构”异构环境指的是计算系统中包含不同类型和架构的计算资源的情况。这些计算资源可能拥有不同的体系结构、处理器类型、内存层次结构、加速器等。在异构环境中,系统可以包含多个不同类型的硬件设备,例如:CPU(CentralProcessingUnit):通用的中央处理器,负责执行通用计算......
  • C 语言用户输入详解:scanf、fgets、内存地址解析及实用指南
    C语言中的用户输入您已经学习了printf()函数用于在C语言中输出值。要获取用户输入,可以使用scanf()函数://声明一个整数变量,用于存储我们从用户那里获得的数字intmyNum;//提示用户输入一个数字printf("请输入一个数字:\n");//获取并保存用户输入的数字scanf("%d"......
  • Golang Defer 必会知识点
    Golang中的一个关键字,用于延迟执行指定的函数调用。在程序执行到defer语句时,该函数调用会被推迟到当前函数返回之前执行,无论当前函数是正常返回还是发生异常退出。Defer语句可以用来在函数执行完毕后清理资源,确保资源的释放不会被遗漏。通过使用defer,我们能够更好地管理和控......