今天是6月10號,記得自己是去年這個時候開始接觸CUDA的,目前只會寫一點簡單的程序,對順程序的優化設計,還是一個小萌新,我在實驗室的項目裏面,看到的CUDA 程序使用了多個流的設計,但是它的時間線卻讓我匪夷所思,爲什麼不是並行的呢?帶着這個疑惑,我決定在最近這段時間,通過Google,搞明白這個問題,然後分享給大家,希望有朝一日,碰巧會有一個少年,碰巧也是通信CS 領域,碰巧也要用CUDA做加速計算,碰巧看到了我這篇文章,那麼,我的所作所爲就有意義了,幫助他節省了一段時間,最重要的,在他剛開始接觸這個領域的時候,給了他一股學下去的興趣~
1.cuda單個流的應用
話不多說,先看一個Nsight裏面的Timeline
圖中Streams裏面表示了一個流裏面的執行順序,基本上就是Memory和Compute的結合,畢竟,拷貝數據和計算,就是我們想要完成的事;
這是一個完成數組相加的cuda程序,基本的思路是“拷貝a到GPU,拷貝b到GPU,計算,拷貝c到CPU”
核函數如下,每次可以算1024個點,採用4個block,每個block256個thread,一個thread算一個點;
__global__ void kernel(int *a, int *b, int *c)
{
int idx = threadIdx.x + blockDim.x * blockIdx.x;
if (idx < N)
{
c[idx] = a[idx] + b[idx];
}
}
然後,現在我把數組的大小設置爲1024*4,就需要核函數執行四次,從而算出結果;這是一個有着先後順序的任所以,將這些任務順序的丟到流裏面,就會按照順序進行下去;
(本人使用的是cuda10.2+vs2015)
第一部分完整代碼:
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <stdlib.h>
#define N 1024*1024
__global__ void kernel(int *a, int *b, int *c)
{
int idx = threadIdx.x + blockDim.x * blockIdx.x;
if (idx < N)
{
c[idx] = a[idx] + b[idx];
}
}
int main()
{
//查詢設備是否支持overlap
cudaDeviceProp prop;
int whichDevice;
cudaGetDevice(&whichDevice);
cudaGetDeviceProperties(&prop, whichDevice);
if (!prop.deviceOverlap)
{
printf("Devices will not handle overlaps");
return 0;
}
int FULL_DATA_SIZE = N * 4;
cudaEvent_t start, stop;
float elapsedTime;
//啓動計時器
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);
//初始化流
cudaStream_t stream;
cudaStreamCreate(&stream);
//數據分配操作
int *host_a, *host_b, *host_c;
int *dev_a, *dev_b, *dev_c;
//在 GPU 上分配內存
cudaMalloc((void**)&dev_a, N * sizeof(int));
cudaMalloc((void**)&dev_b, N * sizeof(int));
cudaMalloc((void**)&dev_c, N * sizeof(int));
//在 CPU 上分配內存,是流使用的頁鎖定內存
cudaHostAlloc((void**)&host_a, FULL_DATA_SIZE * sizeof(int), cudaHostAllocDefault);
cudaHostAlloc((void**)&host_b, FULL_DATA_SIZE * sizeof(int), cudaHostAllocDefault);
cudaHostAlloc((void**)&host_c, FULL_DATA_SIZE * sizeof(int), cudaHostAllocDefault);
//隨機數填充主機內存
for (int i = 0; i < FULL_DATA_SIZE; i++)
{
host_a[i] = rand();
host_b[i] = rand();
}
//在整體數據上循環,每個數據塊的大小是 N
for (int i = 0; i < FULL_DATA_SIZE; i += N)
{
//將鎖定內存以異步方式複製到設備上
cudaMemcpyAsync(dev_a, host_a + i, N * sizeof(int), cudaMemcpyHostToDevice, stream);
cudaMemcpyAsync(dev_b, host_b + i, N * sizeof(int), cudaMemcpyHostToDevice, stream);
kernel << <N / 256, 256, 0, stream >> > (dev_a, dev_b, dev_c);
//將數據複製到鎖定內存中
cudaMemcpyAsync(host_c + i, dev_c, N * sizeof(int), cudaMemcpyDeviceToHost, stream);
}
//同步
cudaStreamSynchronize(stream);
//
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsedTime, start, stop);
//釋放流和內存
cudaFreeHost(host_a);
cudaFreeHost(host_b);
cudaFreeHost(host_c);
cudaFree(dev_a);
cudaFree(dev_b);
cudaFree(dev_c);
//銷燬流
cudaStreamDestroy(stream);
return 0;
}
2020.6.17更新
2.cuda多個流的應用
首先,可以通過cudaSample查詢一下GPU支持幾個流;
打開這個目錄下的vs,
“…\ProgramData\NVIDIA Corporation\CUDA Samples\v10.2\1_Utilities\deviceQuery”
圖中的MultiProcessor代表實際可以使用的流的個數
突然接到了項目任務,這一塊以後再更新吧!