TRT2-CUDA_Runtime_Api-1 驱动

对于runtimeAPI而言,与driver最大的区别就是懒加载。

就是第一个runtimeApi调用的时候,会进行cuInit初始化,避免驱动api的初始化窘境。

第一个需要context的Api调用的时候,也会进行调用cuDevicePrimaryCtxRetain。

Cuda Runtime Api是封装了Driver Api的更高级别的,更友好的Api。


// CUDA运行时头文件
#include <cuda_runtime.h>

// CUDA驱动头文件
#include <cuda.h>
#include <stdio.h>
#include <string.h>

#define checkRuntime(op)  __check_cuda_runtime((op), #op, __FILE__, __LINE__)

bool __check_cuda_runtime(cudaError_t code, const char* op, const char* file, int line){
    if(code != cudaSuccess){    
        const char* err_name = cudaGetErrorName(code);    
        const char* err_message = cudaGetErrorString(code);  
        printf("runtime error %s:%d  %s failed. \n  code = %s, message = %s\n", file, line, op, err_name, err_message);   
        return false;
    }
    return true;
}

int main(){

    CUcontext context = nullptr;
    cuCtxGetCurrent(&context);//因为没创建,没有create
    printf("Current context = %p,当前无context\n", context);

    // cuda runtime是以cuda为基准开发的运行时库
    // cuda runtime所使用的CUcontext是基于cuDevicePrimaryCtxRetain函数获取的
    // 即,cuDevicePrimaryCtxRetain会为每个设备关联一个context,通过cuDevicePrimaryCtxRetain函数可以获取到
    // 而context初始化的时机是懒加载模式,即当你调用一个runtime api时,会触发创建动作
    // 也因此,避免了cu驱动级别的init和destroy操作。使得api的调用更加容易
    int device_count = 0;
    //触发cuInit
    checkRuntime(cudaGetDeviceCount(&device_count));//获取有多少显卡,这里显示1是有一个
    printf("device_count = %d\n", device_count);

    // 取而代之,是使用setdevice来控制当前上下文,当你要使用不同设备时
    // 使用不同的device id
    // 注意,context是线程内作用的,其他线程不相关的, 一个线程一个context stack
    int device_id = 0;
    //触发cuDevicePrimaryCtxRetain
    printf("set current device to : %d,这个API依赖CUcontext,触发创建并设置\n", device_id);
    checkRuntime(cudaSetDevice(device_id));

    // 注意,是由于set device函数是“第一个执行的需要context的函数”,所以他会执行cuDevicePrimaryCtxRetain
    // 并设置当前context,这一切都是默认执行的。注意:cudaGetDeviceCount是一个不需要context的函数
    // 你可以认为绝大部分runtime api都是需要context的,所以第一个执行的cuda runtime函数,会创建context并设置上下文
    cuCtxGetCurrent(&context);
    printf("SetDevice after, Current context = %p,获取当前context\n", context);

    int current_device = 0;
    checkRuntime(cudaGetDevice(&current_device));
    printf("current_device = %d\n", current_device);
    return 0;
}

 首先当前无context是因为在最初没有创建,根本就没有context。

而后来的cudaGetDeviceCount就相当于触发cuInit,cudaSetDevice(device_id)就相当于触发cuDevicePrimaryCtxRetain。

所以后来才可以获得到Current context的值。

Memory

所以一般称shared是片上内存 

作为host memory 内存条而言 其实分为两大类(仅仅作为逻辑区分,实际上物理层面是一个东西)

1、pageable memory 可分页内存

2、page lock memory 页锁定内存

可以理解为page lock memory 是VIP 房间,只留给你一个人住。而page lock memory是普通房间,当所需要存储的东西过多时,就会将你的普通房间空出来,给其他数据住,而作为被请出来的我们,自然就跑到硬盘上了。这种情况就会造成房间很多的假象。但实际上性能降低了。

所以pinned memory具有锁定的特性,相当于每一次去房间都能找到你。

基于前面的理解,我们总结如下:

  1. pinned memory具有锁定特性,是稳定不会被交换的(这很重要,相当于每次去这个房间都一定能找到你)
  2. pageable memory没有锁定特性,对于第三方设备(比如GPU),去访问时,因为无法感知内存是否被交换,可能得不到正确的数据(每次去房间找,说不准你的房间被人交换了)        
  3. pageable memory的性能比pinned memory差,很可能降低你程序的优先级然后把内存交换给别人用
  4. pageable memory策略能使用内存假象,实际8GB但是可以使用15GB,提高程序运行数量(不是速度)
  5. pinned memory太多,会导致操作系统整体性能降低(程序运行数量减少),8GB就只能用8GB。注意不是你的应用程序性能降低,这一点一般都是废话,不用当回事
  6. GPU可以直接访问pinned memory而不能访问pageable memory(因为第二条)

  • 对于页(Page)型可分页内存(Pageable Memory)而言,在将数据发送给硬盘后,当当前数据调用结束并不会自动将被换出的数据调回内存。
  • 页型可分页内存是一种内存管理机制,它允许操作系统将不经常使用或暂时不需要的页面移出物理内存,并将其交换到磁盘上作为虚拟内存的一部分。这样可以释放物理内存供其他进程或数据使用,同时保留只有在需要时才被换入内存。
  • 当某个数据页被换出到磁盘上时,它所占用的物理内存空间就被释放出来,可以用于其他目的。而当该数据再次被需要时,操作系统会从磁盘上将其换入内存,以供程序访问。这个过程通常涉及磁盘I/O操作,因此与直接在内存中访问数据相比,其延迟较高。
  • 需要注意的是,具体的页面置换策略由操作系统决定。不同的操作系统可能采用不同的算法和策略来管理分页内存。常见的页面置换算法包括最近最久未使用(LRU)、先进先出(FIFO)等。
  • 因此,除非被换出的数据再次被访问到,否则操作系统不会主动将其调回到内存中。如果您需要在数据被换出后再次访问该数据,应确保通过适当的方式触发对数据的访问,以便操作系统将其从磁盘交换回内存。

 对于数据的传输,一般的pageable是先传输给pinned再给device

而对于已经pinned可以直接传输。就会很省事。

// CUDA运行时头文件
#include <cuda_runtime.h>

#include <stdio.h>
#include <string.h>

#define checkRuntime(op)  __check_cuda_runtime((op), #op, __FILE__, __LINE__)

bool __check_cuda_runtime(cudaError_t code, const char* op, const char* file, int line){
    if(code != cudaSuccess){    
        const char* err_name = cudaGetErrorName(code);    
        const char* err_message = cudaGetErrorString(code);  
        printf("runtime error %s:%d  %s failed. \n  code = %s, message = %s\n", file, line, op, err_name, err_message);   
        return false;
    }
    return true;
}

int main(){

    int device_id = 0;
    checkRuntime(cudaSetDevice(device_id));

    //global memory
    float* memory_device = nullptr;
    checkRuntime(cudaMalloc(&memory_device, 100 * sizeof(float))); // pointer to device

    //pageable memory
    float* memory_host = new float[100];
    memory_host[2] = 520.25;
    checkRuntime(cudaMemcpy(memory_device, memory_host, sizeof(float) * 100, cudaMemcpyHostToDevice)); // 返回的地址是开辟的device地址,存放在memory_device

    //pinned memory
    float* memory_page_locked = nullptr;
    checkRuntime(cudaMallocHost(&memory_page_locked, 100 * sizeof(float))); // 返回的地址是被开辟的pin memory的地址,存放在memory_page_locked
    checkRuntime(cudaMemcpy(memory_page_locked, memory_device, sizeof(float) * 100, cudaMemcpyDeviceToHost)); // 把device的值传递给pinned  memory

    printf("%f\n", memory_page_locked[2]);//520.250000 打印出来的pinned和pageable一样
    checkRuntime(cudaFreeHost(memory_page_locked));
    delete [] memory_host;
    checkRuntime(cudaFree(memory_device)); 

    return 0;
}

Memory总结如下: 

  • GPU可以直接访问pinned memory,称之为(DMA Direct Memory Access)
  • 对于GPU访问而言,距离计算单元越近,效率越高,所以PinnedMemory<GlobalMemory<SharedMemory
  • 代码中,由new、malloc分配的,是pageable memory,由cudaMallocHost分配的是PinnedMemory,由cudaMalloc分配的是GlobalMemory
  • 尽量多用PinnedMemory储存host数据,或者显式处理Host到Device时,用PinnedMemory做缓存,都是提高性能的关键
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值