CUDA流(Stream)

CUDA流表示一个GPU操作队列,该队列中的操作将以添加到流中的先后顺序而依次执行。可以将一个流看做是GPU上的一个任务,不同任务可以并行执行。使用CUDA流,首先要选择一个支持设备重叠(Device Overlap)功能的设备,支持设备重叠功能的GPU能够在执行一个CUDA核函数的同时,还能在主机和设备之间执行复制数据操作。


支持重叠功能的设备的这一特性很重要,可以在一定程度上提升GPU程序的执行效率。一般情况下,CPU内存远大于GPU内存,对于数据量比较大的情况,不可能把CPU缓冲区中的数据一次性传输给GPU,需要分块传输,如果能够在分块传输的同时,GPU也在执行核函数运算,这样的异步操作,就用到设备的重叠功能,能够提高运算性能。


以下程序演示单个流的使用步骤,对比使用流操作的性能提升,不使用流的情况:

[cpp]  view plain  copy
  1. #include "cuda_runtime.h"    
  2. #include <iostream>  
  3. #include <stdio.h>    
  4. #include <math.h>    
  5.   
  6. #define N (1024*1024)    
  7. #define FULL_DATA_SIZE N*20    
  8.   
  9. __global__ void kernel(int* a, int *b, int*c)  
  10. {  
  11.     int threadID = blockIdx.x * blockDim.x + threadIdx.x;  
  12.   
  13.     if (threadID < N)  
  14.     {  
  15.         c[threadID] = (a[threadID] + b[threadID]) / 2;  
  16.     }  
  17. }  
  18.   
  19. int main()  
  20. {  
  21.     //启动计时器  
  22.     cudaEvent_t start, stop;  
  23.     float elapsedTime;  
  24.     cudaEventCreate(&start);  
  25.     cudaEventCreate(&stop);  
  26.     cudaEventRecord(start, 0);  
  27.   
  28.     int *host_a, *host_b, *host_c;  
  29.     int *dev_a, *dev_b, *dev_c;  
  30.   
  31.     //在GPU上分配内存  
  32.     cudaMalloc((void**)&dev_a, FULL_DATA_SIZE * sizeof(int));  
  33.     cudaMalloc((void**)&dev_b, FULL_DATA_SIZE * sizeof(int));  
  34.     cudaMalloc((void**)&dev_c, FULL_DATA_SIZE * sizeof(int));  
  35.   
  36.     //在CPU上分配可分页内存  
  37.     host_a = (int*)malloc(FULL_DATA_SIZE * sizeof(int));  
  38.     host_b = (int*)malloc(FULL_DATA_SIZE * sizeof(int));  
  39.     host_c = (int*)malloc(FULL_DATA_SIZE * sizeof(int));  
  40.   
  41.     //主机上的内存赋值  
  42.     for (int i = 0; i < FULL_DATA_SIZE; i++)  
  43.     {  
  44.         host_a[i] = i;  
  45.         host_b[i] = FULL_DATA_SIZE - i;  
  46.     }  
  47.   
  48.     //从主机到设备复制数据  
  49.     cudaMemcpy(dev_a, host_a, FULL_DATA_SIZE * sizeof(int), cudaMemcpyHostToDevice);  
  50.     cudaMemcpy(dev_b, host_b, FULL_DATA_SIZE * sizeof(int), cudaMemcpyHostToDevice);  
  51.   
  52.     kernel << <FULL_DATA_SIZE / 1024, 1024 >> > (dev_a, dev_b, dev_c);  
  53.   
  54.     //数据拷贝回主机  
  55.     cudaMemcpy(host_c, dev_c, FULL_DATA_SIZE * sizeof(int), cudaMemcpyDeviceToHost);  
  56.   
  57.     //计时结束  
  58.     cudaEventRecord(stop, 0);  
  59.     cudaEventSynchronize(stop);  
  60.     cudaEventElapsedTime(&elapsedTime, start, stop);  
  61.   
  62.     std::cout << "消耗时间: " << elapsedTime << std::endl;  
  63.   
  64.     //输出前10个结果  
  65.     for (int i = 0; i < 10; i++)  
  66.     {  
  67.         std::cout << host_c[i] << std::endl;  
  68.     }  
  69.   
  70.     getchar();  
  71.   
  72.     cudaFreeHost(host_a);  
  73.     cudaFreeHost(host_b);  
  74.     cudaFreeHost(host_c);  
  75.   
  76.     cudaFree(dev_a);  
  77.     cudaFree(dev_b);  
  78.     cudaFree(dev_c);  
  79.   
  80.     return 0;  
  81. }  


使用流:

[cpp]  view plain  copy
  1. #include "cuda_runtime.h"    
  2. #include <iostream>  
  3. #include <stdio.h>    
  4. #include <math.h>    
  5.   
  6. #define N (1024*1024)    
  7. #define FULL_DATA_SIZE N*20    
  8.   
  9. __global__ void kernel(int* a, int *b, int*c)  
  10. {  
  11.     int threadID = blockIdx.x * blockDim.x + threadIdx.x;  
  12.   
  13.     if (threadID < N)  
  14.     {  
  15.         c[threadID] = (a[threadID] + b[threadID]) / 2;  
  16.     }  
  17. }  
  18.   
  19. int main()  
  20. {  
  21.     //获取设备属性  
  22.     cudaDeviceProp prop;  
  23.     int deviceID;  
  24.     cudaGetDevice(&deviceID);  
  25.     cudaGetDeviceProperties(&prop, deviceID);  
  26.   
  27.     //检查设备是否支持重叠功能  
  28.     if (!prop.deviceOverlap)  
  29.     {  
  30.         printf("No device will handle overlaps. so no speed up from stream.\n");  
  31.         return 0;  
  32.     }  
  33.   
  34.     //启动计时器  
  35.     cudaEvent_t start, stop;  
  36.     float elapsedTime;  
  37.     cudaEventCreate(&start);  
  38.     cudaEventCreate(&stop);  
  39.     cudaEventRecord(start, 0);  
  40.   
  41.     //创建一个CUDA流  
  42.     cudaStream_t stream;  
  43.     cudaStreamCreate(&stream);  
  44.   
  45.     int *host_a, *host_b, *host_c;  
  46.     int *dev_a, *dev_b, *dev_c;  
  47.   
  48.     //在GPU上分配内存  
  49.     cudaMalloc((void**)&dev_a, N * sizeof(int));  
  50.     cudaMalloc((void**)&dev_b, N * sizeof(int));  
  51.     cudaMalloc((void**)&dev_c, N * sizeof(int));  
  52.   
  53.     //在CPU上分配页锁定内存  
  54.     cudaHostAlloc((void**)&host_a, FULL_DATA_SIZE * sizeof(int), cudaHostAllocDefault);  
  55.     cudaHostAlloc((void**)&host_b, FULL_DATA_SIZE * sizeof(int), cudaHostAllocDefault);  
  56.     cudaHostAlloc((void**)&host_c, FULL_DATA_SIZE * sizeof(int), cudaHostAllocDefault);  
  57.   
  58.     //主机上的内存赋值  
  59.     for (int i = 0; i < FULL_DATA_SIZE; i++)  
  60.     {  
  61.         host_a[i] = i;  
  62.         host_b[i] = FULL_DATA_SIZE - i;  
  63.     }  
  64.   
  65.     for (int i = 0; i < FULL_DATA_SIZE; i += N)  
  66.     {  
  67.         cudaMemcpyAsync(dev_a, host_a + i, N * sizeof(int), cudaMemcpyHostToDevice, stream);  
  68.         cudaMemcpyAsync(dev_b, host_b + i, N * sizeof(int), cudaMemcpyHostToDevice, stream);  
  69.   
  70.         kernel << <N / 1024, 1024, 0, stream >> > (dev_a, dev_b, dev_c);  
  71.   
  72.         cudaMemcpyAsync(host_c + i, dev_c, N * sizeof(int), cudaMemcpyDeviceToHost, stream);  
  73.     }  
  74.   
  75.     // wait until gpu execution finish    
  76.     cudaStreamSynchronize(stream);  
  77.   
  78.     cudaEventRecord(stop, 0);  
  79.     cudaEventSynchronize(stop);  
  80.     cudaEventElapsedTime(&elapsedTime, start, stop);  
  81.   
  82.     std::cout << "消耗时间: " << elapsedTime << std::endl;  
  83.   
  84.     //输出前10个结果  
  85.     for (int i = 0; i < 10; i++)  
  86.     {  
  87.         std::cout << host_c[i] << std::endl;  
  88.     }  
  89.   
  90.     getchar();  
  91.   
  92.     // free stream and mem    
  93.     cudaFreeHost(host_a);  
  94.     cudaFreeHost(host_b);  
  95.     cudaFreeHost(host_c);  
  96.   
  97.     cudaFree(dev_a);  
  98.     cudaFree(dev_b);  
  99.     cudaFree(dev_c);  
  100.   
  101.     cudaStreamDestroy(stream);  
  102.     return 0;  
  103. }  

首先声明一个Stream,可以把不同的操作放到Stream内,按照放入的先后顺序执行。

cudaMemcpyAsync操作只是一个请求,表示在流中执行一次内存复制操作,并不能确保cudaMemcpyAsync函数返回时已经启动了复制动作,更不能确定复制操作是否已经执行完成,可以确定的是放入流中的这个复制动作一定是在其后 放入流中的其他动作之前完成的。使用流(同时要使用页锁定内存)和不使用流的结果一致,运算时间分别是30ms和50ms。

猜你喜欢

转载自blog.csdn.net/qq_30263737/article/details/80572116