OpenCL使用CL_MEM_USE_HOST_PTR存储器对象属性与存储器映射
随着OpenCL的普及,现在有越来越多的移动设备以及平板、超级本等都支持OpenCL异构计算。而这些设备与桌面计算机、服务器相比而言性能不是占主要因素的,反而能耗更受人关注。因此,这些移动设备上的GPU与CPU基本都是在同一芯片上(SoC),或者GPU就已经成为了处理器的一部分,像Intel Ivy Bridge架构开始的处理器(Intel HD Graphics 4000开始支持OpenCL),AMD APU等。
因此,在这些设备上做OpenCL的异构并行计算的话,我们不需要像桌面端那些独立GPU那样,要把主存数据通过PCIe搬运到GPU端,然后等GPU计算结束后再搬回到主存。我们只需要将给GPU端分配的显存映射到主机端即可。这样,在主机端我们也能直接通过指针来操作这块存储数据。
下面编写了一个比较简单的例子来描述如何使用OpenCL的存储器映射特性。这个例子在MacBook Air,OS X 10.9.2下完成,并通过Xcode 5.1,Apple LLVM 5.1的编译与运行。 硬件环境为:Intel Core i7 4650U, Intel Graphics 5000, 8GB DDR3L, 128GB SSD
这是主机端代码(C源文件):
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h> #ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif int main(void)
{
cl_int ret; cl_platform_id platform_id = NULL;
cl_device_id device_id = NULL;
cl_context context = NULL;
cl_command_queue command_queue = NULL;
cl_mem memObj = NULL;
char *kernelSource = NULL;
cl_program program = NULL;
cl_kernel kernel = NULL;
int *pHostBuffer = NULL; clGetPlatformIDs(, &platform_id, NULL);
if(platform_id == NULL)
{
puts("Get OpenCL platform failed!");
goto FINISH;
} clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_GPU, , &device_id, NULL);
if(device_id == NULL)
{
puts("No GPU available as a compute device!");
goto FINISH;
} context = clCreateContext(NULL, , &device_id, NULL, NULL, &ret);
if(context == NULL)
{
puts("Context not established!");
goto FINISH;
} command_queue = clCreateCommandQueue(context, device_id, , &ret);
if(command_queue == NULL)
{
puts("Command queue cannot be created!");
goto FINISH;
} // 指定内核源文件路径
const char *pFileName = "/Users/zennychen/Downloads/test.cl"; FILE *fp = fopen(pFileName, "r");
if (fp == NULL)
{
puts("The specified kernel source file cannot be opened!");
goto FINISH;
}
fseek(fp, , SEEK_END);
const long kernelLength = ftell(fp);
fseek(fp, , SEEK_SET); kernelSource = malloc(kernelLength); fread(kernelSource, , kernelLength, fp);
fclose(fp); program = clCreateProgramWithSource(context, , (const char**)&kernelSource, (const size_t*)&kernelLength, &ret);
ret = clBuildProgram(program, , &device_id, NULL, NULL, NULL);
if (ret != CL_SUCCESS)
{
size_t len;
char buffer[ * ]; printf("Error: Failed to build program executable!\n");
clGetProgramBuildInfo(program, device_id, CL_PROGRAM_BUILD_LOG, sizeof(buffer), buffer, &len);
printf("%s\n", buffer);
goto FINISH;
} kernel = clCreateKernel(program, "test", &ret);
if(kernel == NULL)
{
puts("Kernel failed to create!");
goto FINISH;
} const size_t contentLength = sizeof(*pHostBuffer) * * ; // 以下为在主机端分配输入缓存
pHostBuffer = malloc(contentLength); // 然后对此工作缓存进行初始化
for(int i = ; i < * ; i++)
pHostBuffer[i] = i + ; // 这里预分配的缓存大小为4MB,第一个参数是读写的
memObj = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, contentLength, pHostBuffer, &ret);
if(memObj == NULL)
{
puts("Memory object1 failed to create!");
goto FINISH;
} ret = clSetKernelArg(kernel, , sizeof(cl_mem), (void*)&memObj); if(ret != CL_SUCCESS)
{
puts("Set arguments error!");
goto FINISH;
} // 做存储器映射
int *pDeviceBuffer = clEnqueueMapBuffer(command_queue, memObj, CL_TRUE, CL_MAP_READ | CL_MAP_WRITE, , contentLength, , NULL, NULL, &ret);
if(pDeviceBuffer == NULL)
{
puts("Memory map failed!");
goto FINISH;
}
if(pDeviceBuffer != pHostBuffer)
{
// 若从GPU端映射得到的存储器地址与原先主机端的不同,则将数据从主机端传递到GPU端
ret = clEnqueueWriteBuffer(command_queue, memObj, CL_TRUE, , contentLength, pHostBuffer, , NULL, NULL);
if(ret != CL_SUCCESS)
{
puts("Data transfer failed");
goto FINISH;
} /** 如果主机端与设备端地址不同,我们不妨测试一下设备端存储器的Cache情况 */ // 先测试主机端的时间
int sum = ; // 先过一遍存储器
for(int j = ; j < ; j++)
sum += pHostBuffer[j]; time_t t1 = time(NULL);
for(int i = ; i < ; i++)
{
for(int j = ; j < ; j++)
sum += pHostBuffer[j];
}
time_t t2 = time(NULL);
printf("The host delta time is: %f. The value is: %d\n", difftime(t2, t1), sum); // 测试设备端
sum = ; // 先过一遍存储器
for(int j = ; j < ; j++)
sum += pDeviceBuffer[j]; t1 = time(NULL);
for(int i = ; i < ; i++)
{
for(int j = ; j < ; j++)
sum += pDeviceBuffer[j];
}
t2 = time(NULL);
printf("The device delta time is: %f. The value is: %d\n", difftime(t2, t1), sum);
}
else
{
// 若主机端与设备端存储器地址相同,我们仅仅做CPU端测试
int sum = ; // 先过一遍存储器
for(int j = ; j < ; j++)
sum += pHostBuffer[j]; time_t t1 = time(NULL);
for(int i = ; i < ; i++)
{
for(int j = ; j < ; j++)
sum += pHostBuffer[j];
}
time_t t2 = time(NULL);
printf("The host delta time is: %f. The value is: %d\n", difftime(t2, t1), sum);
} // 这里指定将总共有1024 * 1024个work-item
ret = clEnqueueNDRangeKernel(command_queue, kernel, , NULL, (const size_t[]){ * }, NULL, , NULL, NULL); // 做次同步,这里偷懒,不用wait event机制了~
clFinish(command_queue); // 做校验
for(int i = ; i < * ; i++)
{
if(pDeviceBuffer[i] != (i + ) * )
{
puts("Result error!");
break;
}
} puts("Compute finished!"); FINISH: /* Finalization */
if(pHostBuffer != NULL)
free(pHostBuffer); if(kernelSource != NULL)
free(kernelSource); if(memObj != NULL)
clReleaseMemObject(memObj); if(kernel != NULL)
clReleaseKernel(kernel); if(program != NULL)
clReleaseProgram(program); if(command_queue != NULL)
clReleaseCommandQueue(command_queue); if(context != NULL)
clReleaseContext(context); return ;
}
以下是OpenCL内核源代码:
__kernel void test(__global int *pInOut)
{
int index = get_global_id(); pInOut[index] += pInOut[index];
}
另外,主机端代码部分中,OpenCL源文件路径是写死的。各位朋友可以根据自己环境来重新指定路径。
当然,我们还可以修改主机端“clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, contentLength, pHostBuffer, &ret);”这段创建存储器对象的属性。比如,将CL_MEM_USE_HOST_PTR去掉。然后可以再试试效果。
倘若clCreateBuffer的flags参数用的是CL_MEM_ALLOC_HOST_PTR,那么其host_ptr参数必须为空。在调用clEnqueueMapBuffer之后,可以根据其返回的缓存地址,对存储区域做数据初始化。
CL_MEM_ALLOC_HOST_PTR表示应用程序暗示OpenCL实现从主机端可访问的存储空间给设备端分配存储缓存。这个与CL_MEM_USE_HOST_PTR还是有所区别的。CL_MEM_USE_HOST_PTR是完全从应用端当前的内存池分配存储空间;而CL_MEM_ALLOC_HOST_PTR对于CPU与GPU共享主存的环境下,可以在CPU端留下一个访问GPU端VRAM的入口点。我们通过以下程序来测试当前环境的OpenCL实现(以下代码在调用调用了clEnqueueMapBuffer函数之后做了缓存数据初始化的时间比较):
long deltaTimes[];
for(int i = ; i < ; i++)
{
struct timeval tBegin, tEnd;
gettimeofday(&tBegin, NULL);
for(int i = ; i < * ; i++)
pDeviceBuffer[i] = i + ;
gettimeofday(&tEnd, NULL);
deltaTimes[i] = * (tEnd.tv_sec - tBegin.tv_sec ) + tEnd.tv_usec - tBegin.tv_usec;
}
long useTime = deltaTimes[];
for(int i = ; i < ; i++)
{
if(useTime > deltaTimes[i])
useTime = deltaTimes[i];
}
printf("Device memory time spent: %ldus\n", useTime);
int *pHostBuffer = malloc(contentLength);
for(int i = ; i < ; i++)
{
struct timeval tBegin, tEnd;
gettimeofday(&tBegin, NULL);
for(int i = ; i < * ; i++)
pHostBuffer[i] = i + ;
gettimeofday(&tEnd, NULL);
deltaTimes[i] = * (tEnd.tv_sec - tBegin.tv_sec ) + tEnd.tv_usec - tBegin.tv_usec;
}
useTime = deltaTimes[];
for(int i = ; i < ; i++)
{
if(useTime > deltaTimes[i])
useTime = deltaTimes[i];
}
printf("Host memory time spent: %ldus\n", useTime);
其中,对gettimeofday的调用需要包含头文件<sys/time.h>。这个函数所返回的时间可以精确到μs(微秒)。
在Intel Core i7 4650U, Intel Graphics 5000环境下,花费时间差不多,都是2.6ms(毫秒)。因此,在内核真正执行的时候为了清空这部分存储空间的Cache,驱动还是要做点工作的。当然,驱动也可为这块内存区域分配Write-Combined类型的存储器,这样主机端对这部分数据的访问不会被Cache,尽管速度会慢很多,但是通过non-temporal Stream方式读写还是会很不错。况且大部分OpenCL应用对同一块内存数据的读写都只有一次,这么做也不会造成Cache污染。
OpenCL使用CL_MEM_USE_HOST_PTR存储器对象属性与存储器映射的更多相关文章
- 探究@property申明对象属性时copy与strong的区别
一.问题来源 一直没有搞清楚NSString.NSArray.NSDictionary--属性描述关键字copy和strong的区别,看别人的项目中属性定义有的用copy,有的用strong.自己在开 ...
- [源码]Literacy 快速反射读写对象属性,字段
Literacy 说明 Literacy使用IL指令生成方法委托,性能方面,在调用次数达到一定量的时候比反射高很多 当然,用IL指令生成一个方法也是有时间消耗的,所以在只使用一次或少数几次的情况,不但 ...
- 了解JavaScript 对象属性的标签
对象属性的标签 value(属性值), writable(属性可写), enumerable(属性可枚举), configurable(属性可配置), 这些属性标签使对象所持有的属性体现出不同的特性, ...
- 区分元素特性attribute和对象属性property
× 目录 [1]定义 [2]共有 [3]例外[4]特殊[5]自定义[6]混淆[7]总结 前面的话 其实attribute和property两个单词,翻译出来都是属性,但是<javascript高 ...
- JavaScript对象属性(一)
对象object 对象和数组很相似,数组是通过索引来访问和修改数据,对象是通过属性来访问和修改数据的. 这是一个示例对象: var cat = { "name": "W ...
- JS中isPrototypeOf 和hasOwnProperty 的区别 ------- js使用in和hasOwnProperty获取对象属性的区别
JS中isPrototypeOf 和hasOwnProperty 的区别 1.isPrototypeOf isPrototypeOf是用来判断指定对象object1是否存在于另一个对象object2的 ...
- 采用重写tostring方法使ComboBox显示对象属性
当ComboBox中添加的是对象集合的时候,如果运行就会发现显示是的命令空间.类名,而如果我们想显示对象属性名的时候,我们就可以在对象类中重写object基类中的tostring方法.
- json对象数组按对象属性排序
var array = [ {name: 'a', phone: 1, value: 'val_4'}, {name: 'b', phone: 5, value: 'val_3'}, {name: ' ...
- JavaScript学习10 JS数据类型、强制类型转换和对象属性
JavaScript学习10 JS数据类型.强制类型转换和对象属性 JavaScript数据类型 JavaScript中有五种原始数据类型:Undefined.Null.Boolean.Number以 ...
随机推荐
- php使用播放插件播放m3u8,mp4,flv格式的视频
一.这里我主要是播放m3u8的视频,有两款比较好的插件,swise和ckpalyer,我介绍的是ckplayer,这是在pc端播放的,并且是需要flash支持的,不过现在的最新浏览器都是默认安装的 二 ...
- 用python在屏幕上输出一个杨辉三角
在屏幕上输出一个杨辉三角,代码如下 def yanghui(): L = [1] while True: yield L L.append(0) L = [L[i - 1] + L[i] for i ...
- kernel源代码标记:mainline 、longterm、stable、linux-next、snapshot的含义
mainline .longterm.stable.linux-next.snapshot这些名词,都是linux kernel source的标记名称,可以理解为代号. mainline mainl ...
- vue 的虚拟 DOM 有什么好处?
vue 中的虚拟DOM有什么好处?快! 首先了解浏览器显示网页经历的5个过程 1.解析标签,生成元素树(DOM树) 2.解析样式,生成样式树 3.生成元素与样式的关系 4.生成元素的显示坐标 5.显示 ...
- 搭建一个jumpserver跳板机
1,部署jumpserver 建立阿里云公网源yum仓库 清除缓存重新建立缓存 ip后面直接回车,下面输入y 地址端口账户密码直接回车 ,询问跳过输入y,是否继续输入y 用户名回车,输入自己要设置的密 ...
- Python基础之While循环
一.摘要 本片博文将介绍input()函数和while循环的使用 二.input()函数 函数input() 让程序暂停运行,等待用户输入一些文本.获取用户输入后,Python将其存储在一个变量中,以 ...
- PHP 基础知识-数组
PHP 的数组主要分为: 索引数组 - 带有数字索引的数组 关联数组 - 带有指定键的数组 多维数组 - 包含一个或多个数组的数组 索引数组: 有两种创建索引数组的方法: 索引是自动分配的(索 ...
- hi 北京
一 . 感慨 借着参加北京物联网展会的这次机会,提前找老师批了大概两周的假期.当然也借着这次机会,尝试了第一次坐飞机.第一次来北京.心里也有点小激动,在路上甚至会想,我是不是要重新规划一下我的人生了呢 ...
- 安装卸载JDK
卸载JDK 删除Java的安装目录 删除JAVA_HOME 删除path下关于Java的目录 java-version 安装JDK 百度搜索JDK8,找到下载地址 同意协议 下载电脑对应的版本 双击安 ...
- VS 运行库MT、MD的区别
https://www.jianshu.com/p/f43afc1d5946 VC项目属性→配置属性→C/C++→代码生成→运行时库 可以采用的方式有:多线程(/MT).多线程调试(/MTd).多线程 ...