当前位置: 首页 > news >正文

OpenCL C 内存对象

内存对象是 OpenCL 运行时系统分配和管理的对象,用于在主机(Host) 和设备(Device) 之间传递数据。所有需要在内核中使用的数据(除了简单的标量)都必须封装在内存对象中。

1. 内存对象的类型

OpenCL 主要定义了两种类型的内存对象:

内存对象类型描述主要用途内核中参数类型
Buffer (缓冲区)一个通用的一维数据块,类似于 C 中的 malloc 分配的内存。存储数组、结构体、数值等任何线性数据。__global <type>*__constant <type>*__local <type>*
Image (图像)一个专门为图形处理优化的不透明对象,通常是 2D 或 3D。存储纹理、帧缓冲区等图像数据。支持硬件滤波和特定的寻址模式。image2d_timage3d_tsampler_t

核心区别:

  • Buffer 是简单的字节数组,你可以直接通过指针索引访问任何元素。

  • Image 的内部布局是不透明的(例如,为了优化可能采用瓦片式存储),你不能直接通过指针访问。必须使用专门的内置函数read_imagefwrite_imagef)来读写,并由硬件自动处理格式转换、滤波和边界处理。

2. 内存对象的创建

内存对象由主机端的 API 调用创建。

a) 创建 Buffer 对象

c

// clCreateBuffer 函数原型
cl_mem clCreateBuffer(cl_context context,        // OpenCL 上下文cl_mem_flags flags,        // 内存标志,指定读写权限和分配方式size_t size,               // 缓冲区的大小(字节)void *host_ptr,            // 主机端指针,可用于初始化cl_int *errcode_ret        // 返回错误码
);

重要的 cl_mem_flags

  • CL_MEM_READ_ONLY: Buffer 在内核中只读。

  • CL_MEM_WRITE_ONLY: Buffer 在内核中只写。

  • CL_MEM_READ_WRITE: Buffer 在内核中可读写。

  • CL_MEM_USE_HOST_PTR: 使用 host_ptr 指向的内存作为存储。OpenCL 可能会直接使用这块内存,也可能将其内容拷贝到设备优化过的内存中。

  • CL_MEM_COPY_HOST_PTR: 分配新的设备内存,并将 host_ptr 指向的数据拷贝过去。CL_MEM_READ_ONLY 常与此合用。

  • CL_MEM_ALLOC_HOST_PTR: 申请一块可以被映射(map) 到主机地址空间的内存(“零拷贝”内存的一种)。

标志组合示例:

c

// 典型的输入缓冲区(只读,复制主机数据)
cl_mem_flags input_flags = CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR;// 典型的输出缓冲区(只写)
cl_mem_flags output_flags = CL_MEM_WRITE_ONLY;// 可读写的中间缓冲区
cl_mem_flags rw_flags = CL_MEM_READ_WRITE;// 使用主机内存的缓冲区(零拷贝)
cl_mem_flags use_host_flags = CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR;

创建示例:

c

float host_data[1000] = {...};
cl_int err;// 示例1:创建一个设备上只读的buffer,并用host_data初始化
cl_mem input_buffer = clCreateBuffer(context,CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, // 常用组合sizeof(host_data),host_data,&err
);// 示例2:创建一个设备上只写的buffer,不初始化
cl_mem output_buffer = clCreateBuffer(context,CL_MEM_WRITE_ONLY,1000 * sizeof(float),NULL,&err
);
b) 创建 Image 对象

创建 Image 比 Buffer 更复杂,需要指定格式(Channel Order, Channel Type)和描述(宽度、高度、深度)。

c

// clCreateImage2D (已废弃) 或通用的 clCreateImage
cl_mem clCreateImage(cl_context context,cl_mem_flags flags,const cl_image_format *image_format, // 图像格式描述符const cl_image_desc *image_desc,     // 图像维度描述符void *host_ptr,cl_int *errcode_ret
);

cl_image_format 结构体:

c

typedef struct _cl_image_format {cl_channel_order image_channel_order;   // 通道顺序 (e.g., CL_RGBA, CL_R)cl_channel_type image_channel_data_type; // 通道数据类型 (e.g., CL_FLOAT, CL_UNORM_INT8)
} cl_image_format;

cl_image_desc 结构体: 指定图像类型(2D, 3D)、维度、步幅等。

创建示例:

c

cl_image_format format;
format.image_channel_order = CL_RGBA;       // RGBA 四个通道
format.image_channel_data_type = CL_FLOAT;  // 每个通道是32位浮点数cl_image_desc desc;
desc.image_type = CL_MEM_OBJECT_IMAGE2D;
desc.image_width = 512;
desc.image_height = 512;
desc.image_depth = 0;
// ... 设置其他字段,通常可以默认置0 ...cl_mem input_image = clCreateImage(context,CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,&format,&desc,host_image_data, // 假设host_image_data指向512x512 RGBA float数据&err
);
// 创建 2D 图像
cl_mem create_image_2d(cl_context context, cl_mem_flags flags, const cl_image_format* format,size_t width, size_t height) {cl_int err;cl_mem image = clCreateImage2D(context, flags, format, width, height, 0, NULL, &err);if (err != CL_SUCCESS) {printf("创建 2D 图像失败,错误: %d\n", err);return NULL;}return image;
}// 创建 3D 图像
cl_mem create_image_3d(cl_context context, cl_mem_flags flags,const cl_image_format* format,size_t width, size_t height, size_t depth) {cl_int err;cl_mem image = clCreateImage3D(context, flags, format, width, height, depth, 0, 0, NULL, &err);if (err != CL_SUCCESS) {printf("创建 3D 图像失败,错误: %d\n", err);return NULL;}return image;
}

3. 主机与设备间的数据传输

创建对象后,需要将数据在主机和设备间移动。主要使用 clEnqueueWriteBuffer / clEnqueueReadBuffer 等命令。

a) 写数据到设备 (Host -> Device)

c

// 将主机数据拷贝到之前创建的 input_buffer
err = clEnqueueWriteBuffer(command_queue,   // 命令队列input_buffer,    // 目标buffer对象CL_TRUE,         // 阻塞写入 (CL_TRUE: 等待拷贝完成才返回; CL_FALSE: 异步)0,               // buffer中的偏移量(字节)sizeof(host_data), // 拷贝数据的大小host_data,       // 主机源数据指针0, NULL, NULL    // 事件等待列表和事件对象,用于同步
);
b) 从设备读取数据 (Device -> Host)

c

// 从 output_buffer 读取数据到主机内存
err = clEnqueueReadBuffer(command_queue,output_buffer,CL_TRUE,        // 阻塞读取0,sizeof(host_output_data),host_output_data, // 存放结果的主机内存指针0, NULL, NULL
);

对于 Image,也有对应的 clEnqueueWriteImage 和 clEnqueueReadImage 函数。

显式数据传输
// 主机到设备传输
cl_int write_to_device(cl_command_queue queue, cl_mem buffer, const void* host_ptr, size_t size) {cl_event event;cl_int err = clEnqueueWriteBuffer(queue, buffer, CL_TRUE, 0, size, host_ptr, 0, NULL, &event);if (err == CL_SUCCESS) {clReleaseEvent(event);}return err;
}// 设备到主机传输
cl_int read_from_device(cl_command_queue queue, cl_mem buffer,void* host_ptr, size_t size) {cl_event event;cl_int err = clEnqueueReadBuffer(queue, buffer, CL_TRUE, 0,size, host_ptr, 0, NULL, &event);if (err == CL_SUCCESS) {clReleaseEvent(event);}return err;
}// 非阻塞传输
cl_int write_async(cl_command_queue queue, cl_mem buffer,const void* host_ptr, size_t size, cl_event* event) {return clEnqueueWriteBuffer(queue, buffer, CL_FALSE, 0,size, host_ptr, 0, NULL, event);
}
图像数据传输
// 写入图像数据
cl_int write_image_2d(cl_command_queue queue, cl_mem image,size_t width, size_t height, const void* data) {const size_t origin[3] = {0, 0, 0};const size_t region[3] = {width, height, 1};return clEnqueueWriteImage(queue, image, CL_TRUE, origin, region,0, 0, data, 0, NULL, NULL);
}// 读取图像数据
cl_int read_image_2d(cl_command_queue queue, cl_mem image,size_t width, size_t height, void* data) {const size_t origin[3] = {0, 0, 0};const size_t region[3] = {width, height, 1};return clEnqueueReadImage(queue, image, CL_TRUE, origin, region,0, 0, data, 0, NULL, NULL);
}

4. 在内核中使用内存对象

创建并传输数据后,就可以在内核中使用它们了。

a) 使用 Buffer

内核中将 Buffer 作为指针参数使用,并必须带有地址空间限定符。

c

// Kernel
__kernel void my_buffer_kernel(__global const float* input,  // 对应 cl_mem input_buffer, 用 CL_MEM_READ_ONLY 创建__global float* output,       // 对应 cl_mem output_buffer, 用 CL_MEM_WRITE_ONLY 创建__local float* scratch_mem)   // 局部内存,大小在运行时由主机决定
{size_t gid = get_global_id(0);output[gid] = input[gid] * 2.0f;// ... 可以使用 scratch_mem ...
}

主机设置内核参数时,直接将 cl_mem 对象设进去:

c

clSetKernelArg(kernel, 0, sizeof(cl_mem), &input_buffer);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &output_buffer);
// 对于 __local 参数,只需指定大小,指针为NULL
size_t local_mem_size = 256 * sizeof(float);
clSetKernelArg(kernel, 2, local_mem_size, NULL);
b) 使用 Image

内核中使用专门的 Image 类型和内置函数。

c

// Kernel
__kernel void my_image_kernel(read_only image2d_t srcImage,  // 只读图像对象write_only image2d_t dstImage, // 只写图像对象sampler_t sampler)             // 采样器,定义滤波和寻址模式
{int2 coord = (int2)(get_global_id(0), get_global_id(1));// 从srcImage读取一个像素(使用采样器处理坐标越界、进行滤波等)float4 pixel = read_imagef(srcImage, sampler, coord);// 进行处理(例如,变灰)float gray = 0.2126f * pixel.x + 0.7152f * pixel.y + 0.0722f * pixel.z;// 将结果写入dstImagewrite_imagef(dstImage, coord, (float4)(gray, gray, gray, pixel.w));
}

主机设置参数类似:

c

clSetKernelArg(kernel, 0, sizeof(cl_mem), &input_image);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &output_image);
// 采样器也是内核的一个参数,需要单独创建(clCreateSampler)并设置
clSetKernelArg(kernel, 2, sizeof(cl_sampler), &sampler);

5. 内存映射

内存映射允许主机直接访问设备内存,避免显式拷贝。

// 映射设备内存
void* map_memory(cl_command_queue queue, cl_mem buffer, cl_map_flags flags, size_t size) {cl_event event;void* mapped_ptr;cl_int err = clEnqueueMapBuffer(queue, buffer, CL_TRUE, flags,0, size, 0, NULL, &event, &mapped_ptr);if (err != CL_SUCCESS) {return NULL;}clReleaseEvent(event);return mapped_ptr;
}// 取消映射
cl_int unmap_memory(cl_command_queue queue, cl_mem buffer, void* mapped_ptr) {cl_event event;cl_int err = clEnqueueUnmapMemObject(queue, buffer, mapped_ptr, 0, NULL, &event);if (err == CL_SUCCESS) {clReleaseEvent(event);}return err;
}// 使用内存映射的示例
cl_int use_mapped_memory(cl_command_queue queue, cl_mem buffer, size_t size) {// 映射内存进行写入float* mapped_data = (float*)map_memory(queue, buffer, CL_MAP_WRITE, size);if (!mapped_data) return CL_OUT_OF_RESOURCES;// 直接操作映射的内存for (size_t i = 0; i < size / sizeof(float); i++) {mapped_data[i] = (float)i;}// 取消映射return unmap_memory(queue, buffer, mapped_data);
}

6. 子缓冲区 (Sub-Buffers)

子缓冲区允许将一个大缓冲区划分为多个小缓冲区。

// 创建子缓冲区
cl_mem create_sub_buffer(cl_mem parent_buffer, cl_mem_flags flags,size_t origin, size_t size) {cl_buffer_region region;region.origin = origin;region.size = size;cl_int err;cl_mem sub_buffer = clCreateSubBuffer(parent_buffer, flags,CL_BUFFER_CREATE_TYPE_REGION,&region, &err);if (err != CL_SUCCESS) {printf("创建子缓冲区失败,错误: %d\n", err);return NULL;}return sub_buffer;
}// 创建多个子缓冲区
cl_mem* create_sub_buffers(cl_mem parent_buffer, cl_mem_flags flags,size_t total_size, size_t chunk_size, int* num_chunks) {*num_chunks = total_size / chunk_size;cl_mem* sub_buffers = (cl_mem*)malloc(*num_chunks * sizeof(cl_mem));for (int i = 0; i < *num_chunks; i++) {cl_buffer_region region;region.origin = i * chunk_size;region.size = chunk_size;cl_int err;sub_buffers[i] = clCreateSubBuffer(parent_buffer, flags,CL_BUFFER_CREATE_TYPE_REGION,&region, &err);if (err != CL_SUCCESS) {printf("创建子缓冲区 %d 失败\n", i);// 清理已创建的子缓冲区for (int j = 0; j < i; j++) {clReleaseMemObject(sub_buffers[j]);}free(sub_buffers);return NULL;}}return sub_buffers;
}

7. 内存对象信息查询

// 获取内存对象信息
void print_mem_object_info(cl_mem mem_obj) {cl_mem_object_type type;cl_mem_flags flags;size_t size;void* host_ptr;cl_context context;clGetMemObjectInfo(mem_obj, CL_MEM_TYPE, sizeof(type), &type, NULL);clGetMemObjectInfo(mem_obj, CL_MEM_FLAGS, sizeof(flags), &flags, NULL);clGetMemObjectInfo(mem_obj, CL_MEM_SIZE, sizeof(size), &size, NULL);clGetMemObjectInfo(mem_obj, CL_MEM_HOST_PTR, sizeof(host_ptr), &host_ptr, NULL);clGetMemObjectInfo(mem_obj, CL_MEM_CONTEXT, sizeof(context), &context, NULL);printf("内存对象信息:\n");printf("  类型: %s\n", (type == CL_MEM_OBJECT_BUFFER) ? "缓冲区" :(type == CL_MEM_OBJECT_IMAGE2D) ? "2D图像" :(type == CL_MEM_OBJECT_IMAGE3D) ? "3D图像" : "未知");printf("  大小: %zu bytes\n", size);printf("  标志: 0x%X\n", flags);printf("  主机指针: %p\n", host_ptr);
}// 获取图像格式信息
void print_image_format_info(cl_mem image) {cl_image_format format;clGetImageInfo(image, CL_IMAGE_FORMAT, sizeof(format), &format, NULL);printf("图像格式:\n");printf("  通道顺序: %u\n", format.image_channel_order);printf("  通道类型: %u\n", format.image_channel_data_type);
}

8. 内存对象生命周期管理

内存对象是引用计数的。创建时引用计数为1。

  • clRetainMemObject: 增加引用计数。

  • clReleaseMemObject: 减少引用计数。当计数为0时,对象被释放,内存被回收。

c

clRetainMemObject(input_buffer); // 很少需要手动调用,除非多线程共享
// ... 使用 input_buffer ...
clReleaseMemObject(input_buffer);
clReleaseMemObject(output_buffer);
// 释放所有关联的内存对象是主机程序的责任

9. 完整示例:向量操作

#include <CL/cl.h>
#include <stdio.h>
#include <stdlib.h>#define DATA_SIZE 1024const char* get_cl_error_string(cl_int error) {// 简化的错误字符串映射switch (error) {case CL_SUCCESS: return "成功";case CL_INVALID_VALUE: return "无效值";case CL_OUT_OF_HOST_MEMORY: return "主机内存不足";case CL_OUT_OF_RESOURCES: return "资源不足";default: return "未知错误";}
}int main() {cl_int err;cl_platform_id platform;cl_device_id device;cl_context context;cl_command_queue queue;cl_program program;cl_kernel kernel;// 1. 初始化 OpenCLclGetPlatformIDs(1, &platform, NULL);clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);context = clCreateContext(NULL, 1, &device, NULL, NULL, &err);queue = clCreateCommandQueue(context, device, 0, &err);// 2. 创建内存对象float* host_data = (float*)malloc(DATA_SIZE * sizeof(float));for (int i = 0; i < DATA_SIZE; i++) {host_data[i] = (float)i;}// 输入缓冲区(只读,复制主机数据)cl_mem input_buffer = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,DATA_SIZE * sizeof(float), host_data, &err);// 输出缓冲区(只写)cl_mem output_buffer = clCreateBuffer(context,CL_MEM_WRITE_ONLY,DATA_SIZE * sizeof(float),NULL, &err);// 3. 创建和编译内核const char* kernel_source = "__kernel void vector_double(__global const float* input, \n""                           __global float* output) {     \n""    int i = get_global_id(0);                           \n""    output[i] = input[i] * 2.0f;                        \n""}                                                       \n";program = clCreateProgramWithSource(context, 1, &kernel_source, NULL, &err);clBuildProgram(program, 1, &device, NULL, NULL, NULL);kernel = clCreateKernel(program, "vector_double", &err);// 4. 设置内核参数clSetKernelArg(kernel, 0, sizeof(cl_mem), &input_buffer);clSetKernelArg(kernel, 1, sizeof(cl_mem), &output_buffer);// 5. 执行内核size_t global_size = DATA_SIZE;size_t local_size = 64;clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_size, &local_size, 0, NULL, NULL);// 6. 读取结果float* results = (float*)malloc(DATA_SIZE * sizeof(float));clEnqueueReadBuffer(queue, output_buffer, CL_TRUE, 0,DATA_SIZE * sizeof(float), results, 0, NULL, NULL);// 7. 验证结果int correct = 1;for (int i = 0; i < DATA_SIZE; i++) {if (results[i] != host_data[i] * 2.0f) {correct = 0;break;}}printf("计算结果: %s\n", correct ? "正确" : "错误");// 8. 显示内存对象信息printf("\n输入缓冲区信息:\n");print_mem_object_info(input_buffer);printf("\n输出缓冲区信息:\n");print_mem_object_info(output_buffer);// 9. 清理资源free(host_data);free(results);clReleaseMemObject(input_buffer);clReleaseMemObject(output_buffer);clReleaseKernel(kernel);clReleaseProgram(program);clReleaseCommandQueue(queue);clReleaseContext(context);return 0;
}

总结

函数用途说明
clCreateBuffer创建缓冲区创建一维数据数组
clCreateImage2D创建2D图像创建二维图像对象
clCreateSubBuffer创建子缓冲区创建缓冲区的子区域
clEnqueueRead/WriteBuffer数据传输主机设备间数据拷贝
clEnqueueMapBuffer内存映射主机直接访问设备内存
clEnqueueCopyBuffer内存复制设备内数据拷贝

OpenCL 内存对象是连接主机和设备数据的桥梁。其使用流程可以概括为:

  1. 创建:在主机上使用 clCreateBuffer/clCreateImage 创建内存对象。

  2. 传输:使用 clEnqueueWrite* 将输入数据从主机传输到设备。

  3. 设置参数:使用 clSetKernelArg 将内存对象绑定到内核参数。

  4. 执行:将内核入队执行,设备处理数据。

  5. 回读:使用 clEnqueueRead* 将结果数据从设备传输回主机。

  6. 释放:使用 clReleaseMemObject 释放不再需要的内存对象。

http://www.dtcms.com/a/360190.html

相关文章:

  • 第2.5节:中文大模型(文心一言、通义千问、讯飞星火)
  • 在线图片特效工具,600种创意模板
  • Python入门教程:常用第三方库Matplotlib(基本用法)下载、安装、参数解析教程
  • 案例——从零开始搭建 ASP.NET Core 健康检查实例
  • 【MLLM】语音端到端大模型和Voice Agent发展
  • 【Java进阶】Java与SpringBoot线程池深度优化指南
  • GitHub 热榜项目 - 日榜(2025-08-31)
  • 【AI编程工具】使用Cursor快速搭建一套小型项目管理系统
  • mysql5.7.44安装遇到登录权限问题
  • 在Linux环境安装Maven(保姆级别)
  • macos调用chrome后台下载wasm-binaries.tar.xz
  • k8s---prometheus 监控
  • AI大模型实战解析-RAG知识库+LangChain项目实战
  • 《SVA断言系统学习之路》【01】即时断言概览
  • IDM(Internet Download Managerv 6.38)破除解版下载!IDM 下载器永久免费版!提升下载速度达5倍!安装及使用
  • 深入解析Linux系统中的/etc/hosts文件
  • 刷题日记0831
  • 盲孔轴旋转编码器轴设计与加工的几个注意事项
  • 网络爬虫是自动从互联网上采集数据的程序
  • 开源知识抽取框架 推荐
  • Python基础之元组列表集合字典
  • 数据化管理是什么意思?企业该如何进行数据化管理
  • 介绍GSPO:一种革命性的语言模型强化学习算法
  • 【系统分析师】高分论文:论信息系统的安全与保密设计
  • 利用爬虫获取淘宝商品信息,参数解析
  • 大语言模型(LLM)简介与应用分享
  • Linux 系统忘记 root 密码?紧急救援方案与原理详解
  • 【STM32】外部中断(下)
  • kkfile一键部署-ubuntu版
  • Transformer中的核心概念III-Attention