OpenCL中的内存对象

OpenCL内存模型

众所周知,OpenCL使用设备(Device)端的内存进行计算。为了更高效地完成计算,OpenCL内存模型中划分了具有不同特性的内存,如下图所示。
OpenCL内存模型
其中:

  • 主机内存: 用于完成CL代码端所需数据的读入和写出;
  • 全局内存:所有的工作组可见,读写速度慢,可存储量大;
  • 常量内存:所有工作组可见,在核函数内部只读读取速度快,但是存储量有限制
  • 局部内存:仅在每个工作组内可见,存储量有限制;在部分厂商的设备中,此模块的访问速度较快,可用于缓存全局数据以减少数据读取时间,具体使用此内存缓存全局数据能否加速访存,建议查询设备商的开发者手册
  • 私有内存:各工作项内部使用的变量,通常位于寄存器上,很快,但有限;不能使用太多,否则会导致部分数据被放置到全局内存

此外,现在的GPU大部分都有纹理内存,它的性质和全局内存类似,但主要用于存储图像数据。其最大的特点在于支持使用硬件插值,读入数据时使用纹理缓存,对图像数据的读取较全局内存快

OpenCL中跟buffer内存相关的函数

获取内存对象的信息

//
// 获取内存对象属性信息
//  param_name: 属性索引值可以是如下的值:
//    1)CL_MEM_TYPE:     获取内存对象类型
//    2)CL_MEM_FLAGS:    获取内存对象flags标记值
//    3)CL_MEM_SIZE :    获取内存对象数据大小
//    4)CL_MEM_HOST_PTR: 获取内存对象映射的CPU内存地址
//    5)CL_MEM_CONTEXT:  获取内存对象上下文
//    ......
//
cl_int clGetMemObjectInfo(
   cl_mem buffer,                      // 缓冲区对象
   cl_mem_info param_name,             // 属性索引
   size_t param_value_size,            // 属性值大小
   void * param_value,                 // 输出属性值
   size_t *param_value_size_ret);      // 返回错误码

这里列出的属性只是常见的几个,在OpenCL的官方文档里面,你能查询到更多的内存属性索引值,这对于查找内存相关的错误非常有用。

内存的创建与释放

//
// 创建内存对象
// flags : 核函数操作内存的标志,可同时使用多个;
// size : 想要分配的内存大小,为 length * sizeof(ElemType);
// host_ptr: 对应的主机端内存的指针,搭配flag使用,可不填
//
cl_mem clCreateBuffer(
    cl_context context,    // CL上下文
    cl_mem_flags flags,    // 缓冲区创建标记
    size_t size,           // 缓冲区数据大小 
    void * host_ptr,       // 缓冲区在CPU内存中的地址
    cl_int *errcode_ref);  // 返回错误码

//
// 释放内存对象
//
cl_int clReleaseMemObject(cl_mem buffer);

这里值得注意的是创建内存时标志位(flags)的选择,论坛里有一篇很不错的帖子:openCL缓存对象的传输与映射,它包含了两部分内容:

  • 核函数对此内存的访问权限(并不限制主机端):
    • 读写(CL_MEM_READ_WRITE,默认)
    • 只写(CL_MEM_WRITE_ONLY)
    • 只读(CL_MEM_READ_ONLY)
  • 主机端内存与设备端内存的相互关系:
    • 使用主机端已有内存初始化(CL_MEM_COPY_HOST_PTR),需要host_ptr不为空
    • 将主机端内存拷贝至设备端并创建内存(CL_MEM_USE_HOST_PTR),需要host_ptr不为空,建议在具有单独显存的设备上使用
    • 零拷贝内存,主机端和设备端内存直接映射(CL_MEM_ALLOC_HOST_PTR)

设备端与主机端数据的传输

//
// 通过命令队列,向缓冲区对象上传数据,这个相当于将内存中数据上传到GPU显存
// 注意:
//  1) event: 是用于异步上传数据,blocking_write必须为false,等数据上传完成后触发
//  2)上传的数据范围:将内存中 (ptr+offset) ~ (ptr+offset+cb) 这部分数据上传
//
cl_int clEnqueueWriteBuffer(
   cl_command_queue command_queue,     // 命令队列
   cl_mem buffer,                      // 要上传的缓冲区对象
   cl_bool blocking_write,             // 是否同步上传,如果为true,当前函数阻塞到上传完
   size_t offset,                      // 上传数据的开始偏移地址
   size_t cb,                          // 上传数据字节数
   void * ptr,                         // 要上传的CPU中内存数据地址
   cl_uint num_events_in_wait_list,    // 要等待的事件数量  
   const cl_event * event_wait_list,   // 要等待的事件列表
   cl_event *event)                    // 上传完成后触发的事件,当为异步上传时用到

//
// 通过命令队列,从缓冲区对象下载数据,这个相当于将 GPU显存中数据下载到CPU内存
//
cl_int clEnqueueReadBuffer(
   cl_command_queue command_queue,     // 命令队列
   cl_mem buffer,                      // 要下载的缓冲区对象
   cl_bool blocking_read,              // 是否同步下载,如果为true,当前函数阻塞到下载完
   size_t offset,                      // 下载数据的开始偏移地址
   size_t cb,                          // 下载数据字节数
   void * ptr,                         // 要下载的CPU中内存数据地址
   cl_uint num_events_in_wait_list,    // 要等待的事件数量  
   const cl_event * event_wait_list,   // 要等待的事件列表
   cl_event *event);                   // 下载完成后触发的事件,当为异步下载时用到

注意:从设备端内存下载数据时,需要先确认所有数据都已从设备端下载至主机端后,在从主机端读出数据,否则可能会导致错误。它可以通过数据同步实现,其中有两种方法:

  • (1)调用所有clEnqueueReadBuffer语句后,在主机端处理下载的数据前,使用clFinish函数强制等之前的所有语句都处理结束;
  • (2)将blocking_read选项设置为True,确保函数返回时数据下载已经完成。

当然,OpenCL中也有只传输部分数据的“子缓冲区”对象,用于交换部分数据,没有用过,就不班门弄斧了

调用实例

这里没有新的东西,因此,调用实例参考前面的文章即可。

调用纹理内存的image对象和内存内容拷贝

这部分内容比较重要,但是前人已经写得比较详细了,我就不赘述。入门信息可以参考,华叔的博客:OpenCL开发——(7)Image对象和采样器。本文仅额外补充以下两个部分的内容:

参考

除文中标注内容外,还额外参考了华叔的博客:OpenCL开发——(5)内存对象 - 华叔-视觉魔术师的文章 - 知乎

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值