OpenCL 双调排序 GPU 版
▶ 参考书中的代码,写了
● 代码,核函数文件包含三中算法
// kernel.cl
__kernel void bitonicSort01(__global uint *data, const uint stage, const uint subStage, const uint direction)// 基本的元素对调整
{
const uint gid = get_global_id();
const uint isAscend = ((gid / ( << stage)) % ) ? - direction : direction; // 判断本工作项的元素对应该排成升序还是降序
const uint distance = << (stage - subStage); // 元素对下标差
const uint lid = (gid / distance) * distance * + gid % distance; // 寻找元素对的左右元素
const uint rid = lid + distance;
const uint lElement = data[lid], rElement = data[rid];
if (lElement > rElement && isAscend || lElement < rElement && !isAscend) // 不符合排序要求,交换两元素
data[lid] = rElement, data[rid] = lElement;
} __kernel void bitonicSort02(__global uint *data, const uint stage, const uint subStage, const uint direction, __local uint *localMem)// 使用局部内存调整
{
const uint gid = get_global_id(), mid = get_local_id(); // 同 bitonicSort01
const uint isAscend = ((gid / ( << stage)) % ) ? - direction : direction;
const uint distance = << (stage - subStage);
const uint lid = (gid / distance) * distance * + gid % distance;
const uint rid = lid + distance; localMem[mid * + ] = data[lid]; // 读取 data 的时候读进局部内存,与局部内存相关的下标用的都是 mid 而不是 gid
localMem[mid * + ] = data[rid];
barrier(CLK_LOCAL_MEM_FENCE); if (localMem[mid * + ] > localMem[mid * + ] && isAscend || localMem[mid * + ] < localMem[mid * + ] && !isAscend)
data[lid] = localMem[mid * + ], data[rid] = localMem[mid * + ];
} #define STRIDE 4 // aux 中四个元素一组,表示一个工作项的元素对索引和值,依照 main.c 中给定的第 5 参数的大小进行相等的调整 __kernel void bitonicSort03(__global uint *data, const uint stage, const uint subStage, const uint direction, __local uint *localMem, __local uint *aux)// 使用两个局部内存,感觉多此一举?
{
const uint gid = get_global_id(), mid = get_local_id(); // 同 bitonicSort02
const uint isAscend = ((gid / ( << stage)) % ) ? - direction : direction;
const uint distance = << (stage - subStage);
const uint lid = (gid / distance) * distance * + gid % distance;
const uint rid = lid + distance; localMem[mid * + ] = data[lid];
localMem[mid * + ] = data[rid];
barrier(CLK_LOCAL_MEM_FENCE); aux[mid * STRIDE + ] = lid; // 开始向aux 中填充
aux[mid * STRIDE + ] = rid;
if (localMem[mid * + ] > localMem[mid * + ] && isAscend || localMem[mid * + ] < localMem[mid * + ] && !isAscend)
aux[mid * STRIDE + ] = localMem[mid * + ], aux[mid * STRIDE + ] = localMem[mid * + ];
else
aux[mid * STRIDE + ] = localMem[mid * + ], aux[mid * STRIDE + ] = localMem[mid * + ];
barrier(CLK_LOCAL_MEM_FENCE); data[aux[mid * STRIDE + ]] = aux[mid * STRIDE + ], data[aux[mid * STRIDE + ]] = aux[mid * STRIDE + ]; // 向原数组中填充数据
/*// 书中的填充方法,一个工作组仅使用一个工作项串行地向原数组中填充,美名曰“无冲突”,实际上花费了 5 倍的时间
if (mid == 0)
{
for (int i = 0; i < get_local_size(0); i++)
data[aux[i * STRIDE + 0]] = aux[i * STRIDE + 1], data[aux[i * STRIDE + 2]] = aux[i * STRIDE + 3];
}
*/
}
// main.c
#include <stdio.h>
#include <stdlib.h>
#include <cl.h> //#define PRINT_RESULT // 输出排序前后的数组元素(数据量较大时不用)
#define ASCENDING 1 // 升序
#define DESCENDING 0 // 降序
#define DATA_SIZE (1<<20) // 数据规模
#define GROUP_SIZE 128 // 工作组大小 const char *sourceText = "D:/Code/OpenCL/OpenCLProjectTemp/OpenCLProjectTemp/kernel.cl";
const unsigned int sortOrder = ASCENDING; // ASCENDING / DESCENDING int readText(const char* kernelPath, char **pcode)// 读取文本文件放入 pcode,返回字符串长度
{
FILE *fp;
int size;
//printf("<readText> File: %s\n", kernelPath);
fopen_s(&fp, kernelPath, "rb");
if (!fp)
{
printf("Open kernel file failed\n");
getchar();
exit(-);
}
if (fseek(fp, , SEEK_END) != )
{
printf("Seek end of file failed\n");
getchar();
exit(-);
}
if ((size = ftell(fp)) < )
{
printf("Get file position failed\n");
getchar();
exit(-);
}
rewind(fp);
if ((*pcode = (char *)malloc(size + )) == NULL)
{
printf("Allocate space failed\n");
getchar();
exit(-);
}
fread(*pcode, , size, fp);
(*pcode)[size] = '\0';
fclose(fp);
return size + ;
} int main()
{
int i;
srand(); cl_int status;
cl_uint nPlatform;
clGetPlatformIDs(, NULL, &nPlatform);
cl_platform_id *listPlatform = (cl_platform_id*)malloc(nPlatform * sizeof(cl_platform_id));
clGetPlatformIDs(nPlatform, listPlatform, NULL);
cl_uint nDevice;
clGetDeviceIDs(listPlatform[], CL_DEVICE_TYPE_ALL, , NULL, &nDevice);
cl_device_id *listDevice = (cl_device_id*)malloc(nDevice * sizeof(cl_device_id));
clGetDeviceIDs(listPlatform[], CL_DEVICE_TYPE_ALL, nDevice, listDevice, NULL);
cl_context context = clCreateContext(NULL, nDevice, listDevice, NULL, NULL, &status);
cl_command_queue queue = clCreateCommandQueue(context, listDevice[], CL_QUEUE_PROFILING_ENABLE, &status); int *hostA = (cl_int*)malloc(DATA_SIZE * sizeof(cl_int));
for (i = ; i < DATA_SIZE; hostA[i++] = rand());
#ifdef PRINT_RESULT // 输出排序前的数组
printf("\n");
for (i = ; i < DATA_SIZE; i++)
{
printf("%5d,", hostA[i]);
if ((i + ) % == )
printf("\n");
}
printf("\n");
#endif
cl_mem deviceA = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, DATA_SIZE * sizeof(cl_int), hostA, &status); char *code;
size_t codeLength = readText(sourceText, &code);
cl_program program = clCreateProgramWithSource(context, , (const char**)&code, &codeLength, &status);
status = clBuildProgram(program, nDevice, listDevice, NULL, NULL, NULL);
if (status)
{
char info[];
clGetProgramBuildInfo(program, listDevice[], CL_PROGRAM_BUILD_LOG, , info, NULL);
printf("\n%s\n", info);
}
//cl_kernel kernel = clCreateKernel(program, "bitonicSort01", &status); // 选择使用的核函数
//cl_kernel kernel = clCreateKernel(program, "bitonicSort02", &status);
cl_kernel kernel = clCreateKernel(program, "bitonicSort03", &status); size_t globalSize = DATA_SIZE / , groupSize = GROUP_SIZE;
cl_uint stageCount, stage, subStage;
for (i = DATA_SIZE, stageCount = ; i > ; stageCount++, i >>= ); // 需要的总轮数
clSetKernelArg(kernel, , sizeof(cl_mem), (void*)&deviceA);
clSetKernelArg(kernel, , sizeof(cl_uint), (void*)&sortOrder);
clSetKernelArg(kernel, , GROUP_SIZE * * sizeof(cl_uint), NULL); // bitonicSort02 和 bitonicSort03 需要的局部内存参数
clSetKernelArg(kernel, , GROUP_SIZE * * sizeof(cl_uint), NULL); // bitonicSort03 需要的局部内存参数 cl_event exeEvent; // 计时用的事件
cl_ulong executionStart, executionEnd, timeCount1, timeCount2; // 计时器, 精确到 ns
for (stage = timeCount1 = timeCount2 = ; stage < stageCount; stage++) // 当前轮数
{
clSetKernelArg(kernel, , sizeof(cl_uint), (void*)&stage);
for (subStage = ; subStage < stage + ; subStage++) // 当前轮中归并的步骤数
{
clSetKernelArg(kernel, , sizeof(cl_uint), (void*)&subStage);
//clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &globalSize, &groupSize, 0, NULL, NULL); // 不计时的核函数调用
clEnqueueNDRangeKernel(queue, kernel, , NULL, &globalSize, &groupSize, , NULL, &exeEvent);
clWaitForEvents(, &exeEvent);
clGetEventProfilingInfo(exeEvent, CL_PROFILING_COMMAND_START, sizeof(cl_ulong), &executionStart, NULL);
clGetEventProfilingInfo(exeEvent, CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &executionEnd, NULL);
timeCount1 += (executionEnd - executionStart) / , timeCount2 += (executionEnd - executionStart) % ;// ms 的整数和小数部分分开记录
}
}
printf("Kernel time: %lu.%lu ms\n", timeCount1 + timeCount2 / , timeCount2 % ); // 内核运行总时间 clEnqueueReadBuffer(queue, deviceA, CL_TRUE, , DATA_SIZE * sizeof(cl_int), hostA, , NULL, NULL);
#ifdef PRINT_RESULT // 输出排序后的结果
printf("\n");
for (i = ; i < DATA_SIZE; i++)
{
printf("%5d,", hostA[i]);
if ((i + ) % == )
printf("\n");
}
printf("\n");
#else
for (i = ; i < DATA_SIZE - ; i++) // 不输出结果,只做检查,有错误的时候输出第一个错误的位置
{
if (sortOrder != (hostA[i + ] >= hostA[i]))
{
printf("Error at i == %d, hostA[i] == %d, hostA[i+1] == %d", i, hostA[i], hostA[i + ]);
break;
}
}
#endif free(listPlatform);
free(listDevice);
clReleaseContext(context);
clReleaseCommandQueue(queue);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseEvent(exeEvent);
free(hostA);
clReleaseMemObject(deviceA);
getchar();
return ;
}
● 输出结果,统一采用 (1<<20) 的数据规模,尝试不同的工作组大小。使用局部内存并没有明显提升,尤其是使用两个局部内存的方法,严重拖后腿。
bitonicSort01
// groupSize = 64
kernels time: 7.941984 ms
// groupSize = 128
kernels time: 7.947360 ms
// groupSize = 256
kernels time: 7.935616 ms
// groupSize = 512
kernels time: 7.919776 ms
// groupSize = 1024
kernels time: 7.970080 ms bitonicSort02
// groupSize = 64
kernels time: 7.980160 ms
// groupSize = 128
kernels time: 7.957984 ms
// groupSize = 256
kernels time: 7.964032 ms
// groupSize = 512
kernels time: 7.959072 ms
// groupSize = 1024
kernels time: 8.517120 ms bitonicSort03
// groupSize = 64
kernels time: 9.901120 ms
// groupSize = 128
kernels time: 9.936384 ms
// groupSize = 256
kernels time: 9.950976 ms
// groupSize = 512
kernels time: 10.134176 ms
// groupSize = 1024
kernels time: 10.533516 ms bitonicSort03(书中的串行写入)
// groupSize = 64
kernels time: 51.590080 ms
// groupSize = 128
kernels time: 31.607904 ms
// groupSize = 256
kernels time: 35.344244 ms
// groupSize = 512
kernels time: 50.841184 ms
// groupSize = 1024
kernels time: 93.284544 ms
● 总结
■ CPU 版双调排序使用递归,代码比较简洁,也可以使用本篇中的方法家拿其转化为循环迭代。GPU暂时不使用递归,主要精力在于不停地向命令队列中入队不同层次的任务,同一层次内的任务可以并行执行
■ 这本书就是垃圾,就 T M D 是 垃 圾!①书里代码引用不全,比如核函数里上来就使用变量 threadId,声明定义都没有;②代码混乱,比如在 bitonicSort01 和 bitonicSort02 里 threadId 的含义是不同的,尤其书中 bitonicSort02 里同时用该变量表示 get_global_id(0) 和 get_local_id(0),一想就知道代码执行结果肯定是错的;③ 机翻,好不容易写到倒数第二章了,翻译都懒得弄了,这里抄一段原文看谁看得懂:“我们基本上执行的操作是引入名为 sharedMem 的变量,并且使用加载这些值的简单策略:每个线程将在共享内存数据存储器中存储两个值(邻近值),在随后的代码部分中读出此线程,并且用于引用全局内存的所有读取操作目前在本地 / 共享内存中执行”。④ 残缺不堪,感觉 bitonicSort03 的优化应该是有一定道理的,但是在这里完全没有看出该优化的目的和优势所在,书中也没有说清楚。
OpenCL 双调排序 GPU 版的更多相关文章
- OpenCL 双调排序 CPU 版
▶ 学习了双调排序,参考(https://blog.csdn.net/xbinworld/article/details/76408595) ● 使用 CPU 排序的代码 #include <s ...
- 【转载】双调排序Bitonic Sort,适合并行计算的排序算法
双调排序是data-independent的排序, 即比较顺序与数据无关的排序方法, 特别适合做并行计算,例如用GPU.fpga来计算. 1.双调序列 在了解双调排序算法之前,我们先来看看什么是双调序 ...
- 三十分钟理解:双调排序Bitonic Sort,适合并行计算的排序算法
欢迎转载,转载请注明:本文出自Bin的专栏blog.csdn.net/xbinworld 技术交流QQ群:433250724,欢迎对算法.技术.应用感兴趣的同学加入 双调排序是data-indepen ...
- DirectX11 With Windows SDK--27 计算着色器:双调排序
前言 上一章我们用一个比较简单的例子来尝试使用计算着色器,但是在看这一章内容之前,你还需要了解下面的内容: 章节 26 计算着色器:入门 深入理解与使用缓冲区资源(结构化缓冲区/有类型缓冲区) Vis ...
- 保姆级教程——Ubuntu16.04 Server下深度学习环境搭建:安装CUDA8.0,cuDNN6.0,Bazel0.5.4,源码编译安装TensorFlow1.4.0(GPU版)
写在前面 本文叙述了在Ubuntu16.04 Server下安装CUDA8.0,cuDNN6.0以及源码编译安装TensorFlow1.4.0(GPU版)的亲身经历,包括遇到的问题及解决办法,也有一些 ...
- Win10 + Python + GPU版MXNet + VS2015 + RTools + R配置
最近入手一台GTX 1070的笔记本,手痒想在win10上试下GPU跑模型,所以就有了接下来的安装GPU版mxnet的坎坷历程,经过多重试验终于搞定了python和R安装mxnet,现将主要点记录如下 ...
- Windows 2012服务器安装GPU版TensorFlow完全攻略
一.首先,推荐用Anaconda安装 因为Anaconda本身就已经默认安装了很多常用的Python库,可以省去大量的库安装过程,并且解决兼容性问题. Anaconda本身的安装也非常简单,搜索Ana ...
- windows7 64位机上配置支持GPU版(CUDA7.5)的OpenCV2.4.13操作步骤
很久之前在windows7 32位上配置过GPU版的opencv,可参考http://blog.csdn.net/fengbingchun/article/details/9831837 Window ...
- 用MXnet实战深度学习之一:安装GPU版mxnet并跑一个MNIST手写数字识别
用MXnet实战深度学习之一:安装GPU版mxnet并跑一个MNIST手写数字识别 http://phunter.farbox.com/post/mxnet-tutorial1 用MXnet实战深度学 ...
随机推荐
- BZOJ3925: [Zjoi2015]地震后的幻想乡【概率期望+状压DP】
Description 傲娇少女幽香是一个很萌很萌的妹子,而且她非常非常地有爱心,很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们. 这不,幻想乡突然发生了地震,所有的道路都崩塌了.现在的首要任 ...
- 最小生成树--prim+优先队列优化模板
prim+优先队列模板: #include<stdio.h> //大概要这些头文件 #include<string.h> #include<queue> #incl ...
- IIS目录
一.目录浏览 一般网站部署后,需要禁用目录浏览, 若启用目录浏览的话,可以自定义开启哪些目录(只能根目录),和影藏哪些目录 iis中限制访问某个文件或某个类型的文件配置方法 注意:图片目录不要隐藏,不 ...
- 【网络通讯】Nat知识了解
一.Nat的含义 NAT(Network Address Translation,网络地址转换)是1994年提出的.当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址 ...
- MySQL--区分表名大小写
============================================================================ 在MySQL中,可以通过lower_case_ ...
- pipelinedb continuous view 操作
continuous view 是 pipelinedb的核心,类似一个view,但是数据是合并了stream以及table的数据输入数据,并且是 实时根据输入数据进行更新的 语法 CREATE CO ...
- StreamSets 相关文章
相关streamsets 文章(不按顺序) 学习视频-百度网盘 StreamSets 设计Edge pipeline StreamSets Data Collector Edge 说明 streams ...
- .NET4.0框架退休日期逐渐临近
微软宣布了.NET框架4.0至4.5.1版本的生命周期终结计划. 2016年1月12日之后,所有的技术支持,包含安全和非安全的更新补丁,都将会停止.开发人员和用户到时候可以选择回退到.NET 3.5 ...
- 在 php 7.3 中 switch 语句中使用 continue
在 php 7.3 中 switch 语句中使用 continue 在 php 7.3 的 switch 中使用 continue 会出现警告.1 2 3 while ($foo) { switch ...
- 3种web会话管理方式:基于server端session方式、cookie-based方式、token-based方式
出处:http://www.cnblogs.com/lyzg/p/6067766.html