最近在做一个关于用OpenCL来处理字符串匹配的小程序,
导师给出的题目是这样的,首先生成一个长度尽可能大的随机字符串(由ATCG这四个字符组成),然后设定1000个长度在2-10之间的随机字符串(也是由ATCG四个字符组成,然后将每个字符小字符串与大字符串去匹配,返回匹配项的下标值。
由于笔者才疏学浅,所以刚开始用于测试的算法是自己写的最笨的穷举法:
由于要和cpu比较运行时间,所以在主程序中:
char* X=(char*)malloc(10000*seizeof(char)); //动态分配大字符串的空间
char**Y=(char**)malloc(1000*seizeof(clar*));//动态分配指向小字符串指针的空间
for(int i=0;i<1000;i++)
{
Y[i]=(char*)malloc(11*seizeof(char)); //动态分配每个小字符串的空间
}
分配空间后,就给他们随机赋值,这里代码省略,
接下来就是匹配代码(返回匹配项代码省略):
void PipeiCPU(char **Y,char *X)
{
int i=0;
for(int j=0;j<1000;j++)
{
int k=0;
while(X[i]!='/0')
{
while(Y[j][k]!='/0')
{
if(Y[j][k]==X[i+k])
k++;
else break;
}
i++;
}
j++;
}
}
好,在主程序下测试成功了,接下来就是写opencl的内核程序,我心想也不难,我设定了1024个work-items,也就是说gpu能够并行执行1024个相同的__kernel函数,我之所以设定1024个work-items的原因是有1000个小字符串,我希望这1000个小字符并行去匹配,这样一来的话效率的提升不就显而易见了。
好,我照着葫芦画瓢
下面是我的kernel程序:
__kernel void PipeiGPU(const __global char *X
const __global char **Y)
{
uint y=get_global_id(0);
if(y<1000)
.........
}
其实kernel里的程序相比主程序反而简单了,少了一层对小字符串指针的遍历,因为y取到了当前工作的work-items的ID,也就是说我们知道了这个kernel在哪个work-item下工作,这样我们就知道这是哪个小字符串了。
好,原本以为一切就绪了,一编译,结果出问题了,编译器不认识const __global char **Y,后来网上一查资料,有人说opencl C是不支持二级指针的,那怎么办?我一下子郁闷了。
后来没办法,由于只能传一级指针,我只能先把前面的小字符串存放到一个指针下,我给这个指针分配了
1000*11+1个字符空间
char *T=(char*)malloc((1000*11+1)seizeof(char));
为什么这么分配,我把每个小字符串长度设定为11,统计出来不满11的,我就在后面加空格,至于最后一个一是个结束符。
然后我还得定义一个指针p,当我从T中取每个小字符串的时候,赋给p
char *P=(char*)malloc(11*seizeof(char));
然后写kernel:
__kernel void PipeiGPU(const __global char* X
const __global char* T
const __global char* P)
{
uint y=get-global_id(0);
int p=0;
for(int i=y*11;i<(y+1)*11;i++)//1:将单个小字符串写到P里
{
if(T[i]!=' ')
{
P[p]=T[i];
p++;
}
else break;
}
P[p]='/0';
int j=0;
while(X[j]!='/0') //2:P与X进行匹配
{
int p=0;
while(P[p]!='/0')
{
if(X[j]==P[p])
p++;
else break;
}
j++;
}
}
终于!!!!执行成功,最后测试发现gpu的速度是cpu速度执行的1.5倍,效果并不是很理想,重要的一点原因是因为在kenrel里多了个1这个步骤,浪费了很多时间,呵呵。
当然,算法方面也有很大的余地,所以接下来笔者准备改良一下算法,希望能找到更好的解决方法,提高更多的效率,呵呵。