运行时环境层
OpenCL Runtime 是一个软件组件,负责在不同平台和硬件设备上执行 OpenCL 程序。它提供了一系列 API 和工具,帮助开发者管理计算设备、创建和编译 OpenCL 程序、调度任务以及进行内存管理。
设备管理:负责发现和管理可用的计算设备(如 CPU、GPU、FPGA 等),并提供接口以查询设备属性;
上下文创建:用于创建和管理 OpenCL 上下文,上下文包含了设备、内存对象、命令队列和程序;
内存管理:提供内存分配和管理功能,包括在设备上分配和释放内存,支持主机与设备之间的数据传输;
程序编译与执行:支持从源代码创建程序对象,并编译为设备可执行的代码。同时负责调度和执行内核;
命令队列管理:提供命令队列的创建和管理功能,允许用户异步地提交计算任务;
事件和同步:处理事件和同步机制,以确保内核和数据传输的正确顺序执行;
代码示例如下:
#define CL_TARGET_OPENCL_VERSION 220
#include <CL/cl.h>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#define ARRAY_SIZE 1024
// OpenCL kernel code for vector addition
const char* kernelSource = "__kernel void vec_add(__global float* A, __global float* B, __global float* C) { \
int id = get_global_id(0); \
C[id] = A[id] + B[id]; \
}";
int main() {
cl_platform_id platform_id;
cl_device_id device_id;
cl_context context;
cl_command_queue queue;
cl_program program;
cl_kernel kernel;
cl_int ret;
// Arrays on the host
float A[ARRAY_SIZE], B[ARRAY_SIZE], C[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++) {
A[i] = i;
B[i] = i * 2;
}
// 1. Get the number of platforms
cl_uint num_platforms;
ret = clGetPlatformIDs(0, NULL, &num_platforms);
if (ret != CL_SUCCESS) {
printf("Failed to get platform IDs\n");
return -1;
}
cl_platform_id* platforms = (cl_platform_id*)malloc(num_platforms * sizeof(cl_platform_id));
ret = clGetPlatformIDs(num_platforms, platforms, NULL);
if (ret != CL_SUCCESS) {
printf("Failed to get platforms\n");
free(platforms);
return -1;
}
// Try to find the NVIDIA platform
for (cl_uint i = 0; i < num_platforms; i++) {
char platform_name[128];
clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, sizeof(platform_name), platform_name, NULL);
printf("Platform %d: %s\n", i, platform_name);
if (strstr(platform_name, "NVIDIA") != NULL) {
platform_id = platforms[i];
printf("Selected NVIDIA platform: %s\n", platform_name);
break;
}
}
if (!platform_id) {
printf("NVIDIA platform not found\n");
free(platforms);
return -1;
}
// 2. Get the GPU device from the selected NVIDIA platform
ret = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_GPU, 1, &device_id, NULL);
if (ret != CL_SUCCESS) {
printf("Failed to get GPU device ID from NVIDIA platform, error code: %d\n", ret);
free(platforms);
return -1;
}
// Print the selected device
char device_name[128];
clGetDeviceInfo(device_id, CL_DEVICE_NAME, sizeof(device_name), device_name, NULL);
printf("Selected device: %s\n", device_name);
// 3. Create a context
context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &ret);
if (ret != CL_SUCCESS) {
printf("Failed to create context\n");
free(platforms);
return -1;
}
printf("Context created successfully.\n");
// 4. Create a command queue
queue = clCreateCommandQueueWithProperties(context, device_id, 0, &ret);
if (ret != CL_SUCCESS) {
printf("Failed to create command queue\n");
free(platforms);
return -1;
}
printf("Command queue created successfully.\n");
// 5. Create a program from the kernel source
program = clCreateProgramWithSource(context, 1, &kernelSource, NULL, &ret);
if (ret != CL_SUCCESS) {
printf("Failed to create program\n");
free(platforms);
return -1;
}
printf("Program created successfully.\n");
// 6. Build the program
ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);
if (ret != CL_SUCCESS) {
printf("Failed to build program\n");
char log[1024];
clGetProgramBuildInfo(program, device_id, CL_PROGRAM_BUILD_LOG, sizeof(log), log, NULL);
printf("Build log:\n%s\n", log);
free(platforms);
return -1;
}
printf("Program built successfully.\n");
// 7. Create the kernel
kernel = clCreateKernel(program, "vec_add", &ret);
if (ret != CL_SUCCESS) {
printf("Failed to create kernel\n");
free(platforms);
return -1;
}
printf("Kernel created successfully.\n");
// 8. Create buffers for the input and output arrays
cl_mem buffer_A = clCreateBuffer(context, CL_MEM_READ_ONLY, ARRAY_SIZE * sizeof(float), NULL, &ret);
cl_mem buffer_B = clCreateBuffer(context, CL_MEM_READ_ONLY, ARRAY_SIZE * sizeof(float), NULL, &ret);
cl_mem buffer_C = clCreateBuffer(context, CL_MEM_WRITE_ONLY, ARRAY_SIZE * sizeof(float), NULL, &ret);
if (ret != CL_SUCCESS) {
printf("Failed to create buffers\n");
free(platforms);
return -1;
}
printf("Buffers created successfully.\n");
// 9. Copy the input data to the respective memory buffers
ret = clEnqueueWriteBuffer(queue, buffer_A, CL_TRUE, 0, ARRAY_SIZE * sizeof(float), A, 0, NULL, NULL);
ret |= clEnqueueWriteBuffer(queue, buffer_B, CL_TRUE, 0, ARRAY_SIZE * sizeof(float), B, 0, NULL, NULL);
if (ret != CL_SUCCESS) {
printf("Failed to write to buffers\n");
free(platforms);
return -1;
}
printf("Data written to buffers successfully.\n");
// 10. Set the kernel arguments
ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void*)&buffer_A);
ret |= clSetKernelArg(kernel, 1, sizeof(cl_mem), (void*)&buffer_B);
ret |= clSetKernelArg(kernel, 2, sizeof(cl_mem), (void*)&buffer_C);
if (ret != CL_SUCCESS) {
printf("Failed to set kernel arguments\n");
free(platforms);
return -1;
}
printf("Kernel arguments set successfully.\n");
// 11. Execute the kernel
size_t global_size = ARRAY_SIZE;
ret = clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_size, NULL, 0, NULL, NULL);
if (ret != CL_SUCCESS) {
printf("Failed to enqueue kernel\n");
free(platforms);
return -1;
}
printf("Kernel enqueued successfully.\n");
// 12. Read the output buffer back to the host
ret = clEnqueueReadBuffer(queue, buffer_C, CL_TRUE, 0, ARRAY_SIZE * sizeof(float), C, 0, NULL, NULL);
if (ret != CL_SUCCESS) {
printf("Failed to read from buffer\n");
free(platforms);
return -1;
}
printf("Data read from buffer successfully.\n");
// Output the results
printf("Result:\n");
for (int i = 0; i < 10; i++) {
printf("C[%d] = %f\n", i, C[i]);
}
// 13. Clean up
clReleaseMemObject(buffer_A);
clReleaseMemObject(buffer_B);
clReleaseMemObject(buffer_C);
clReleaseKernel(kernel);
clReleaseProgram(program);
clReleaseCommandQueue(queue);
clReleaseContext(context);
free(platforms);
printf("Resources released successfully.\n");
return 0;
}
结果:
Platform 0: Intel(R) OpenCL
Platform 1: NVIDIA CUDA
Selected NVIDIA platform: NVIDIA CUDA
Selected device: NVIDIA GeForce RTX 4080 SUPER
Context created successfully.
Command queue created successfully.
Program created successfully.
Program built successfully.
Kernel created successfully.
Buffers created successfully.
Data written to buffers successfully.
Kernel arguments set successfully.
Kernel enqueued successfully.
Data read from buffer successfully.
Result:
C[0] = 0.000000
C[1] = 3.000000
C[2] = 6.000000
C[3] = 9.000000
C[4] = 12.000000
C[5] = 15.000000
C[6] = 18.000000
C[7] = 21.000000
C[8] = 24.000000
C[9] = 27.000000
结果说明:
Platform 0: Intel(R) OpenCL
表示系统上检测到一个 OpenCL 平台,提供商是 Intel,可能是用于 CPU 或集成显卡的 OpenCL 运行时;
Platform 1: NVIDIA CUDA
表示另一个 OpenCL 平台由 NVIDIA 提供,基于 CUDA 技术,能够在 NVIDIA GPU 上运行 OpenCL 程序;
Selected NVIDIA platform: NVIDIA CUDA
程序成功选择了 NVIDIA CUDA 平台,以在 NVIDIA GPU 上运行;
Selected device: NVIDIA GeForce RTX 4080 SUPER
选择的设备是 NVIDIA GeForce RTX 4080 SUPER,这是你的 GPU,程序将在此设备上执行计算;
Context created successfully.
成功创建了 OpenCL 上下文(context),它负责管理设备、内核和内存对象的生命周期;
Command queue created successfully.
成功创建了命令队列,程序通过此队列向 GPU 发送计算任务;
Program created successfully.
OpenCL 程序(从字符串中创建)已成功创建。该程序包含内核代码;
Program built successfully.
内核程序已成功编译和构建,没有语法或其他构建错误;
Kernel created successfully.
程序中定义的内核函数 hello 成功创建,可以在设备上运行;
Buffers created successfully.
程序成功为 GPU 分配了缓冲区(内存对象),这些缓冲区将用于存储输入数据和输出结果;
Data written to buffers successfully.
输入数据已经成功写入 GPU 缓冲区,准备进行计算;
Kernel arguments set successfully.
成功将内核函数的参数设置为相应的 GPU 缓冲区;
Kernel enqueued successfully.
内核已被添加到命令队列中,准备在 GPU 上执行;
Data read from buffer successfully.
内核执行完成后,成功从 GPU 缓冲区中读取结果数据;
Result:
C[0] = 0.000000
C[1] = 3.000000
C[2] = 6.000000
C[3] = 9.000000
C[4] = 12.000000
C[5] = 15.000000
C[6] = 18.000000
C[7] = 21.000000
C[8] = 24.000000
C[9] = 27.000000
这是计算结果,可能是一个简单的线性计算,每个输出值是由内核函数计算得到的。此例中,每个结果都以步长 3 递增,从 0 开始;
Resources released successfully.
程序成功释放了所有分配的资源,包括内核、程序、队列、上下文等。