随着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存储器对象属性与存储器映射的更多相关文章

  1. 探究@property申明对象属性时copy与strong的区别

    一.问题来源 一直没有搞清楚NSString.NSArray.NSDictionary--属性描述关键字copy和strong的区别,看别人的项目中属性定义有的用copy,有的用strong.自己在开 ...

  2. [源码]Literacy 快速反射读写对象属性,字段

    Literacy 说明 Literacy使用IL指令生成方法委托,性能方面,在调用次数达到一定量的时候比反射高很多 当然,用IL指令生成一个方法也是有时间消耗的,所以在只使用一次或少数几次的情况,不但 ...

  3. 了解JavaScript 对象属性的标签

    对象属性的标签 value(属性值), writable(属性可写), enumerable(属性可枚举), configurable(属性可配置), 这些属性标签使对象所持有的属性体现出不同的特性, ...

  4. 区分元素特性attribute和对象属性property

    × 目录 [1]定义 [2]共有 [3]例外[4]特殊[5]自定义[6]混淆[7]总结 前面的话 其实attribute和property两个单词,翻译出来都是属性,但是<javascript高 ...

  5. JavaScript对象属性(一)

    对象object  对象和数组很相似,数组是通过索引来访问和修改数据,对象是通过属性来访问和修改数据的. 这是一个示例对象: var cat = { "name": "W ...

  6. JS中isPrototypeOf 和hasOwnProperty 的区别 ------- js使用in和hasOwnProperty获取对象属性的区别

    JS中isPrototypeOf 和hasOwnProperty 的区别 1.isPrototypeOf isPrototypeOf是用来判断指定对象object1是否存在于另一个对象object2的 ...

  7. 采用重写tostring方法使ComboBox显示对象属性

    当ComboBox中添加的是对象集合的时候,如果运行就会发现显示是的命令空间.类名,而如果我们想显示对象属性名的时候,我们就可以在对象类中重写object基类中的tostring方法.

  8. json对象数组按对象属性排序

    var array = [ {name: 'a', phone: 1, value: 'val_4'}, {name: 'b', phone: 5, value: 'val_3'}, {name: ' ...

  9. JavaScript学习10 JS数据类型、强制类型转换和对象属性

    JavaScript学习10 JS数据类型.强制类型转换和对象属性 JavaScript数据类型 JavaScript中有五种原始数据类型:Undefined.Null.Boolean.Number以 ...

随机推荐

  1. php使用播放插件播放m3u8,mp4,flv格式的视频

    一.这里我主要是播放m3u8的视频,有两款比较好的插件,swise和ckpalyer,我介绍的是ckplayer,这是在pc端播放的,并且是需要flash支持的,不过现在的最新浏览器都是默认安装的 二 ...

  2. 用python在屏幕上输出一个杨辉三角

    在屏幕上输出一个杨辉三角,代码如下 def yanghui(): L = [1] while True: yield L L.append(0) L = [L[i - 1] + L[i] for i ...

  3. kernel源代码标记:mainline 、longterm、stable、linux-next、snapshot的含义

    mainline .longterm.stable.linux-next.snapshot这些名词,都是linux kernel source的标记名称,可以理解为代号. mainline mainl ...

  4. vue 的虚拟 DOM 有什么好处?

    vue 中的虚拟DOM有什么好处?快! 首先了解浏览器显示网页经历的5个过程 1.解析标签,生成元素树(DOM树) 2.解析样式,生成样式树 3.生成元素与样式的关系 4.生成元素的显示坐标 5.显示 ...

  5. 搭建一个jumpserver跳板机

    1,部署jumpserver 建立阿里云公网源yum仓库 清除缓存重新建立缓存 ip后面直接回车,下面输入y 地址端口账户密码直接回车 ,询问跳过输入y,是否继续输入y 用户名回车,输入自己要设置的密 ...

  6. Python基础之While循环

    一.摘要 本片博文将介绍input()函数和while循环的使用 二.input()函数 函数input() 让程序暂停运行,等待用户输入一些文本.获取用户输入后,Python将其存储在一个变量中,以 ...

  7. PHP 基础知识-数组

    PHP 的数组主要分为: 索引数组 - 带有数字索引的数组 关联数组 - 带有指定键的数组 多维数组 - 包含一个或多个数组的数组   索引数组:   有两种创建索引数组的方法: 索引是自动分配的(索 ...

  8. hi 北京

    一 . 感慨 借着参加北京物联网展会的这次机会,提前找老师批了大概两周的假期.当然也借着这次机会,尝试了第一次坐飞机.第一次来北京.心里也有点小激动,在路上甚至会想,我是不是要重新规划一下我的人生了呢 ...

  9. 安装卸载JDK

    卸载JDK 删除Java的安装目录 删除JAVA_HOME 删除path下关于Java的目录 java-version 安装JDK 百度搜索JDK8,找到下载地址 同意协议 下载电脑对应的版本 双击安 ...

  10. VS 运行库MT、MD的区别

    https://www.jianshu.com/p/f43afc1d5946 VC项目属性→配置属性→C/C++→代码生成→运行时库 可以采用的方式有:多线程(/MT).多线程调试(/MTd).多线程 ...