OpenCL如何获取最小线程并行粒度
由于OpenCL是为各类处理器设备而打造的开发标准的计算语言。因此跟CUDA不太一样的是,其对设备特征查询的项更上层,而没有提供一些更为底层的特征查询。比如,你用OpenCL的设备查询API只能获取最大work group size,但无法获取到最小线程并行粒度。
但是,由于最小线程并行粒度对于OpenCL应用领域最广的GPU而言确实是一个比较重要的参数。如果你的work group的work item的个数是最小线程并行粒度的倍数,那么你的OpenCL kernel程序往往会达到很高的计算效率,同时也能基于这个模型来做一些Memory Bank Confliction的避免措施。因此,我这里提供了一个比较简单的OpenCL kernel来获取当前GPU或其它处理器的最小线程并行粒度。
我们知道,一个计算设备由若干个Compute Unit构i成,而一个Compute Unit中包含了多个Processing Element,一个Compute Unit中的所有Processing Element对于一条算术逻辑指令而言是同时进行操作的。而不同的Compute Unit之间也可以是同时进行操作。因此,GPU的并行可以划分为两个层次——一层是Compute Unit内的所有Processing Element的并行操作;另一层是各个Compute Unit的并行操作。
上面是物理层面,如果对于OpenCL逻辑层面,我们可以认为,一个work group的最大work item个数是指一个compute unit最多能调度、分配的线程数。这个数值一般就是一个CU内所包含的PE的个数的倍数。比如,如果一个GPU有2个CU,每个CU含有8个PE,而Max work group size是512,那么说明一个CU至少可以分配供512个线程并发操作所需要的各种资源。由于一个GPU根据一条算术逻辑指令能对所有PE发射若干次作为一个“原子的”发射操作,因此,这一个对程序员而言作为“原子的”发射操作启动了多少个线程,那么我们就可以认为是该GPU的最小并行线程数。如果一款GPU的最小线程并行数是32,那么该GPU将以32个线程作为一组原子的线程组。这意味着,如果遇到分支,那么一组32个线程组中的所有线程都将介入这个分支,对于不满足条件的线程,则会等到这32个线程中其它线程都完成分支处理之后再一起执行下面的指令。
如果我将work group size指定为64,并且在kernel程序里加一个判断,如果pid小于32做操作A,否则做操作B,那么pid为0~31的线程组会执行操作A,而pid为32到63的线程组不会受到阻塞,而会立马执行操作B。此时,两组线程将并发操作(注意,这里是并发,而不是并行。因为上面讲过,GPU一次发射32个线程的话,那么对于多个32线程组将会调度发射指令)。
根据这个特性,我们就可以写一个OpenCL kernel程序来判别当前GPU的最小并行线程粒度。
我们首先会将work group size定为最大能接受的尺寸。然后,我们将这个work group平均划分为两组,对它们进行测试。我们在中间定义了一个local memory的变量,每个线程都能访问它,不过我们只让pid为0以及pid为[max_work_group_size / 2]的线程去访问它,以不受太多干扰。如果这个标志在线程组0执行时被线程组1改变,那么我们就知道这个粒度并非是最小的,然后对前一组再平均划分为2,递归操作。如果在执行线程组0之后标志没有被更改,那么说明这整个线程组是一个原子的线程组,也就是我们所要的最小并行的线程粒度。
在内核程序中,我们还传了一个用于延迟的循环次数,使得非原子的线程组能够被并发执行。
下面的程序的执行环境为:Windows 7 32-bit Home Edition AMD-APU A6-3420M Visual Studio 2013 Express Edition AMD APP SDK
下面先贴主机端的部分代码片断:
- /*Step 3: Create context.*/
- cl_context context = nullptr; // OpenCL context
- cl_command_queue commandQueue = nullptr;
- cl_program program = nullptr; // OpenCL kernel program object that'll be running on the compute device
- cl_mem outputMemObj = nullptr; // output memory object for output
- cl_kernel kernel = nullptr; // kernel object
- const int deviceIndex = ;
- context = clCreateContext(NULL,, &devices[deviceIndex],NULL,NULL,NULL);
- /*Step 4: Creating command queue associate with the context.*/
- commandQueue = clCreateCommandQueue(context, devices[deviceIndex], , NULL);
- /*Step 5: Create program object */
- // Read the kernel code to the buffer
- FILE *fp = fopen("cl_kernel.cl", "rb");
- if(fp == nullptr)
- {
- puts("The kernel file not found!");
- goto RELEASE_RESOURCES;
- }
- fseek(fp, , SEEK_END);
- size_t kernelLength = ftell(fp);
- fseek(fp, , SEEK_SET);
- char *kernelCodeBuffer = (char*)malloc(kernelLength + );
- fread(kernelCodeBuffer, , kernelLength, fp);
- kernelCodeBuffer[kernelLength] = '\0';
- fclose(fp);
- const char *aSource = kernelCodeBuffer;
- program = clCreateProgramWithSource(context, , &aSource, &kernelLength, NULL);
- /*Step 6: Build program. */
- status = clBuildProgram(program, , &devices[deviceIndex], NULL, NULL, NULL);
- /*Step 7: Initial inputs and output for the host and create memory objects for the kernel*/
- cl_int outputArg = ;
- outputMemObj = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(outputArg), NULL, NULL);
- /*Step 8: Create kernel object */
- kernel = clCreateKernel(program,"QueryMinimumGranularity", NULL);
- /*Step 9: Sets Kernel arguments.*/
- cl_int inputArg = ;
- status = clSetKernelArg(kernel, , sizeof(inputArg), &inputArg);
- status = clSetKernelArg(kernel, , sizeof(outputMemObj), &outputMemObj);
- /*Step 10: Running the kernel.*/
- size_t groupSize;
- clGetDeviceInfo(devices[deviceIndex], CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(groupSize), &groupSize, NULL);
- size_t global_work_size[] = { groupSize };
- size_t local_work_size[] = { groupSize };
- status = clEnqueueNDRangeKernel(commandQueue, kernel, , NULL, global_work_size, local_work_size, , NULL, NULL);
- clFinish(commandQueue); // Force wait until the OpenCL kernel is completed
- /*Step 11: Read the cout put back to host memory.*/
- status = clEnqueueReadBuffer(commandQueue, outputMemObj, CL_TRUE, , sizeof(outputArg), &outputArg, , NULL, NULL);
- char chBuffer[];
- wchar_t wsBuffer[];
- sprintf(chBuffer, "The minimum granularity is: %d", outputArg);
- MBString2WCString(wsBuffer, chBuffer, false);
- MessageBox(hWnd, wsBuffer, L"Notice", MB_OK);
下面是kernel代码:
- __kernel void QueryMinimumGranularity(int nLoop, __global int *pOut)
- {
- __local volatile int flag;
- int index = get_global_id();
- int totalItems = get_global_size();
- do
- {
- int halfIndex = totalItems / ;
- if(index == )
- flag = ;
- barrier(CLK_LOCAL_MEM_FENCE);
- if(index < halfIndex)
- {
- for(int i = ; i < nLoop; i++)
- {
- if(flag == -)
- break;
- }
- if(flag != -)
- {
- if(index == )
- {
- *pOut = totalItems;
- flag = ;
- }
- }
- }
- else
- {
- if(index == halfIndex)
- {
- if(flag != )
- {
- //while(flag != 1);
- flag = -;
- }
- }
- }
- barrier(CLK_LOCAL_MEM_FENCE);
- if(flag == )
- break;
- totalItems /= ;
- }
- while(totalItems > );
- }
对于Windows 7小如何做基于AMD APU的OpenCL的开发,可以参考这个贴:
http://www.cnblogs.com/zenny-chen/archive/2013/06/14/3136158.html
OpenCL如何获取最小线程并行粒度的更多相关文章
- C#线程 并行线程
第五部分 并行线程 在本节中,我们将介绍Framework 4.0新增的利用多核处理器的多线程API: 并行LINQ或PLINQ Parallel 类 任务并行性构造 并发集合 自旋锁和自旋等待 ...
- 多线程外排序解决大数据排序问题2(最小堆并行k路归并)
转自:AIfred 事实证明外排序的效率主要依赖于磁盘,归并阶段采用K路归并可以显著减少IO量,最小堆并行k路归并,效率倍增. 二路归并的思路会导致非常多冗余的磁盘访问,两组两组合并确定的是当前的相对 ...
- WPF线程获取UI线程
WPF中只能是UI线程才可以改变UI控件相关,当采用多线程工作时,可用以下代码获取 UI线程进行操作: App.Current.Dispatcher.Invoke((Action)delegate() ...
- [Python]获取子线程异常信息
起因 今天在写东西的时候,用到了多线程.遇到了个问题: 子线程的异常,在父线程中无法捕获. 解决 问题代码 问题代码示例代码如下: import threading class SampleThrea ...
- Java 获取当前线程、进程、服务器ip
/** * 获取当前线程id */ private Long getThreadId() { try { return Thread.currentThread().getId(); } catch ...
- 通过设置线程池的最小线程数来提高task的效率,SetMinThreads。
http://www.cnblogs.com/Charltsing/p/taskpoolthread.html task默认对线程的调度是逐步增加的,连续多次运行并发线程,会提高占用的线程数,而等若干 ...
- 获取其他线程的数据用 queue, 多进程Q
获取其他线程的数据用 queue, 多进程Q
- 《GPU高性能编程CUDA实战》第五章 线程并行
▶ 本章介绍了线程并行,并给出四个例子.长向量加法.波纹效果.点积和显示位图. ● 长向量加法(线程块并行 + 线程并行) #include <stdio.h> #include &quo ...
- 利用进程ID获取主线程ID
利用进程ID获取主线程ID,仅适用于单线程.多线程应区分哪个是主线程,区分方法待验证 (1)好像可以用StartTime最早的,不过通过线程执行时间不一定可靠,要是在最开始就CreateThread了 ...
随机推荐
- phpstorm 习惯设置
phpstorm 习惯设置 1. 字体:Source Code Pro 大小:14 链接: https://pan.baidu.com/s/1HLpbduBHFvbq1a10QV4uCg 提取码: y ...
- Hadoop_25_MapReduce实现日志清洗程序
1.需求: 对web访问日志中的各字段识别切分,去除日志中不合法的记录,根据KPI统计需求,生成各类访问请求过滤数据 2.实现代码: a) 定义一个bean,用来记录日志数据中的各数据字段 packa ...
- 08_Redis通用命令
keys pattern:获取所有与pattern匹配的key,返回所有与该key匹配的keys:通配符:*表示任意0个或多个任意字符,?表示任意一个字符
- JavaScript教程——实例对象与 new 命令
典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念.所谓“类”就是对象的模板,对象就是“类”的实例.但是,JavaScript 语言的对象体系,不是基于“类”的,而 ...
- 最简单之安装azkaban
一,拉取源码构建 git clone https://github.com/azkaban/azkaban.git cd azkaban; ./gradlew build installDist 二, ...
- Kafka集群---分布式消息系统
概念: kafka是一种消息中间件 作用: 解耦.冗余.提高扩展性.缓冲 保证顺序.灵活.削峰填谷 异步通信 kafla角色 producer: 生产者,负责发布消息 consumer: 消费者,负责 ...
- OFDM时域削峰法降峰均比的原理及影响
以下是对实验室师兄答疑的转述,经加工后的文字不可避免的存在一些噪声,仅供参考: 时域削峰为非线性变换,效果上相当于将时域中功率较大值的信号点,减去一个合适的“抵消”信号点的功率,使其降低到所设置的门限 ...
- vue-cli3构建多页面应用2.0
1.0版本点这里 -> 博客:vue-cli3构建多页面应用1.0 github:vue-cli-multipage 在1.0版本上做了以下改进: 1. 增加pages.config.js, ...
- 【线性代数】2-6:三角矩阵( $A=LU$ and $A=LDU$ )
title: [线性代数]2-6:三角矩阵( A=LUA=LUA=LU and A=LDUA=LDUA=LDU ) toc: true categories: Mathematic Linear Al ...
- [JZOJ6347]:ZYB玩字符串(DP+记忆化搜索)
题目描述 $ZYB$获得了一个神秘的非空字符串$p$. 初始时,串$S$是空的. $ZYB$会执行若干次这样的操作: $1.$选取$S$中的一个任意的位置(可以是最前面或者最后面) $2.$在这个位置 ...