前言:对相关基础知识了解的朋友可跳过第一部分,本文求topk的思想是在向量元素求和思想的基础上得到的,读者理清楚后会更容易理解如何求topk。
有图解哦~ ヾ(✿゚▽゚)ノ
主要讲解线程层次和相关内置变量的含义,这对后面的坐标计算很重要。
(上图来自夏令营PPT,可以很清楚地看出线程层次)
主要介绍shared memory与register所在位置,这对理解后面的计算任务思想很重要。
(上图来自夏令营PPT)
这里主要是介绍C语言指针与数组,这两者间的转换也会用于后续计算坐标,本人在用到的时候开始有些遗忘,所以在这里记录一下。(部分知识点参考于谭浩强老师C程序设计(第五版))
对于一维数组int a[10]来说,变量名a即为数组首元素的地址。所以int *p=a;即将该地址赋给了指针变量p。
那么此时要想通过指针p访问数组a中其他元素的值怎么办呢?如下图所示(来自谭浩强老师C程序设计(第五版)):在指针变量上加上对应的偏移量,指针变量就能指向对应的数组元素。
本部分代码来自夏令营所给例程。
核函数如下:
__global__ void _sum_gpu(int *input, int count, int *output)
{
__shared__ int sum_per_block[BLOCK_SIZE];
int temp = 0;
for (int idx = threadIdx.x + blockDim.x * blockIdx.x;
idx < count; idx += gridDim.x * blockDim.x
)
{
temp += input[idx];
}
sum_per_block[threadIdx.x] = temp; //the per-thread partial sum is temp!
__syncthreads();
//**********shared memory summation stage***********
for (int length = BLOCK_SIZE / 2; length >= 1; length /= 2)
{
int double_kill = -1;
if (threadIdx.x < length)
{
double_kill = sum_per_block[threadIdx.x] + sum_per_block[threadIdx.x + length];
}
__syncthreads(); //why we need two __syncthreads() here, and,
if (threadIdx.x < length)
{
sum_per_block[threadIdx.x] = double_kill;
}
__syncthreads(); //....here ?
} //the per-block partial sum is sum_per_block[0]
if (blockDim.x * blockIdx.x < count) //in case that our users are naughty
{
//the final reduction performed by atomicAdd()
if (threadIdx.x == 0) atomicAdd(output, sum_per_block[0]);
}
}
要说清楚这个过程需要各种图(都是夏令营PPT的图或有一定修改)!
本部分代码除核函数外来自夏令营所给例程。
由上述思想我们可以尝试将其用在求topk的任务中,将原来的求和过程变为相互之间比较大小且保留前topk元素,难点在于需要维护数组变量和找到正确的索引值。
这个函数是实现寻找topk的关键之一。代码如下:
__device__ __host__ void insert_value(int* array, int k, int data)
{
for (int i = 0; i < k; i++)
{
if (array[i] == data)
{
return;
}
}
if (data < array[k - 1])
return;
for (int i = k - 2; i >= 0; i--)
{
if (data > array[i])
array[i + 1] = array[i];
else {
array[i + 1] = data;
return;
}
}
array[0] = data;
}
相关代码如下:
#define topk 10
__managed__ int source_array[N];
__managed__ int _1pass_results[topk * GRID_SIZE];
__managed__ int final_results[topk];
__global__ void top_k(int* input, int length, int* output, int k)
{
__shared__ int topk_per_block[BLOCK_SIZE*topk];
int top_kt[topk] = {0};
for(int i=blockDim.x*blockIdx.x+threadIdx.x; i<length; i+=gridDim.x*blockDim.x){
insert_value(top_kt, k, input[i]);
}
for(int i=0; i<topk; i++)
topk_per_block[threadIdx.x*k+i] = top_kt[i];
__syncthreads();
for(int len=BLOCK_SIZE/2; len>=1; len/=2){
if(threadIdx.x<len){
for(int i=0; i<k; i++)
insert_value(topk_per_block+threadIdx.x*k, k, topk_per_block[(threadIdx.x+len)*k+i]);
}
__syncthreads();
}
if(blockDim.x * blockIdx.x < length)
if (threadIdx.x == 0){
for(int i=0; i<k; i++)
output[k*blockIdx.x+i] = topk_per_block[i];
}
}
int main(int argc, char const* argv[])
{
...
top_k <<<GRID_SIZE, BLOCK_SIZE >>>(source_array, N, _1pass_results, topk);
top_k <<<1, BLOCK_SIZE >>>(_1pass_results, topk * GRID_SIZE, final_results, topk);
...
}
其实还可以加上topk在原向量中坐标的寻找哦ヽ(* ̄▽ ̄*)ノ
读者朋友们可以试试在此基础上修改各函数,增加向量中坐标的相关变量完成上述任务。