這幾天看別人的論文,發現一個比較有意思的實現方式。巧用pinned memory,在GPU中實現類似pipeline的功能。在論文中pipeline中,有四個操作:地址生成,數據組裝,數據拷貝和計算。對於地址生成和計算是在GPU中操作的。
詳細的請看一個例子:
1、我們假設有兩個thread block,對於第一個block計算地址空間(在例子中省略了),在第一個block生成地址完成生成一個信號;
2、當第一個block完成功能後,通知cpu端,此時在cpu端組織對應的數據;
3、第二部完成後,把數據拷貝到GPU段,數據拷貝完成後給GPU一個信號
4、GPU中第二個block根據第3部中的信號來計算
當從論文中看到實現方式時,覺得so easy,然後碼代碼,結果卻不對。研究了兩天,並且和論文作者溝通了一番才真正碼代碼實現了上述功能。現在就上代碼來。
#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>
__global__ void pipeline(int *flag_a,int*flag_b,int*Input,int*Out)
{
int idx=threadIdx.x;
if(blockIdx.x==0){
if(0==idx)
flag_a[0]=1; //地址生成ok信號
}
if(blockIdx.x==1){
if(0==idx){
int value = 0;
do
{
asm volatile("ld.global.cg.u32 %0, [%1];" :"=r"(value) :"l"(&flag_b[0]));//數據發送ok信號
} while(value != 1);
}
__syncthreads();
Out[idx]=Input[idx]+idx;
}
}
int main()
{
/*1*/
int *flag_a,*flag_b;
cudaHostAlloc((void**)&flag_a,sizeof(int),cudaHostAllocMapped);
cudaHostAlloc((void**)&flag_b,sizeof(int),cudaHostAllocMapped);
flag_a[0]=0;
flag_b[0]=0;
/*2*/
int*Input,*Out;
int *d_Input,*d_Out;
int*d_float_a,*d_float_b;
Input=(int*)malloc(sizeof(int)*32);
Out=(int*)malloc(sizeof(int)*32);
for(int i=0;i<32;i++){
Input[i]=i;
}
memset(Out,0,sizeof(int)*32);
cudaMalloc((void**)&d_Input,sizeof(int)*32);
cudaMemset(d_Input,0,sizeof(int)*32);
cudaMalloc((void**)&d_Out,sizeof(int)*32);
cudaMemset(d_Out,0,sizeof(int)*32);
cudaHostGetDevicePointer((void **)&d_float_a, (void *)flag_a, 0);
cudaHostGetDevicePointer((void **)&d_float_b, (void *)flag_b, 0);
cudaStream_t stream_kernel,stream_datacopy;
cudaStreamCreate(&stream_kernel);
cudaStreamCreate(&stream_datacopy);
pipeline<<<2,32,0,stream_kernel>>>(d_float_a,d_float_b,d_Input,d_Out);
while(!(1==flag_a[0])){
cudaMemcpyAsync(d_Input,Input,sizeof(int)*32,cudaMemcpyHostToDevice,stream_datacopy);
cudaStreamSynchronize(stream_datacopy);
flag_b[0]=1;
break;
}
cudaStreamSynchronize(stream_kernel);
cudaMemcpy(Out,d_Out,sizeof(int)*32,cudaMemcpyDeviceToHost);
for(int i=0;i<32;i++){
printf("%d:%d\n",i,Out[i]);
}
cudaFreeHost(flag_a);
cudaFreeHost(flag_b);
cudaFree(d_Input);
cudaFree(d_Out);
free(Out);
free(Input);
return 0;
}
運行結果:
lucas@lucas-desktop:~/cuda/new$ ls
Makefile pipeline.cu
lucas@lucas-desktop:~/cuda/new$ make
nvcc -O3 -o pile pipeline.cu
lucas@lucas-desktop:~/cuda/new$ ./pile
0:0
1:2
2:4
3:6
4:8
5:10
6:12
7:14
8:16
9:18
10:20
11:22
12:24
13:26
14:28
15:30
16:32
17:34
18:36
19:38
20:40
21:42
22:44
23:46
24:48
25:50
26:52
27:54
28:56
29:58
30:60
31:62
lucas@lucas-desktop:~/cuda/new$
由於CPU和GPU端常規方式在kernel執行期間不能直接通信,所以利用了pinned memory,並且利用了兩個stream。代碼也是比較簡單,就不詳細說明了。
需要注意的是在kernel段,爲什麼會有:
asm volatile("ld.global.cg.u32 %0, [%1];" :"=r"(value) :"l"(&flag_b[0]));//數據發送ok信號
ptx的代碼呢?
在剛開始的實現中,我的辦法如下:
while(!(1==flag_b[0])){
}
對於此種實現方式,在開始實現while之前,編譯器優化掉flag_b[0]的值,把其值保存在一個寄存器中。使得pinned memory中flag_b有更新,但是kernel中的值並不是最新的,所以纔有ptx的代碼。