C++ 如何并行运行Cuda内核调用和CPU函数?
我有一个程序在GPU上运行,使用CUDA和许多小内核,这意味着在我的CPU上的内核调用需要与在我的GPU上的内核执行大约相同的时间 我想在我的程序循环中添加一个CPU函数,它需要的时间大约与我所有内核的一次迭代相同。我知道,在内核启动后,CPU可以与GPU异步工作,但因为我上次启动内核的时间并不比GPU的时间提前多少,所以在这种情况下,这是没有选择的 所以,我的想法是使用多个线程: 一个线程启动我的GPU内核,另一个线程(或多个其他线程)执行CPU功能并并行运行这两个线程 我创建了一个小示例来测试这个想法:C++ 如何并行运行Cuda内核调用和CPU函数?,c++,cuda,openmp,C++,Cuda,Openmp,我有一个程序在GPU上运行,使用CUDA和许多小内核,这意味着在我的CPU上的内核调用需要与在我的GPU上的内核执行大约相同的时间 我想在我的程序循环中添加一个CPU函数,它需要的时间大约与我所有内核的一次迭代相同。我知道,在内核启动后,CPU可以与GPU异步工作,但因为我上次启动内核的时间并不比GPU的时间提前多少,所以在这种情况下,这是没有选择的 所以,我的想法是使用多个线程: 一个线程启动我的GPU内核,另一个线程(或多个其他线程)执行CPU功能并并行运行这两个线程 我创建了一个小示例来测
#include <unistd.h>
#include <cuda_runtime.h>
#include <cuda_profiler_api.h>
#define THREADS_PER_BLOCK 64
__global__ void k_dummykernel1(const float* a, const float* b, float* c, const int N)
{
const int id = blockIdx.x * blockDim.x + threadIdx.x;
if(id < N)
{
float ai = a[id];
float bi = b[id];
c[id] = powf(expf(bi*sinf(ai)),1.0/bi);
}
}
__global__ void k_dummykernel2(const float* a, const float* b, float* c, const int N)
{
const int id = blockIdx.x * blockDim.x + threadIdx.x;
if(id < N)
{
float bi = b[id];
c[id] = powf(c[id],bi);
}
}
__global__ void k_dummykernel3(const float* a, const float* b, float* c, const int N)
{
const int id = blockIdx.x * blockDim.x + threadIdx.x;
if(id < N)
{
float bi = b[id];
c[id] = logf(c[id])/bi;
}
}
__global__ void k_dummykernel4(const float* a, const float* b, float* c, const int N)
{
const int id = blockIdx.x * blockDim.x + threadIdx.x;
if(id < N)
{
c[id] = asinf(c[id]);
}
}
int main()
{
int N = 10000;
int N2 = N/5;
float *a = new float[N];
float *b = new float[N];
float *c = new float[N];
float *d_a,*d_b,*d_c;
for(int i = 0; i < N; i++)
{
a[i] = (10*(1+i))/(float)N;
b[i] = (i+1)/50.0;
}
cudaMalloc((void**)&d_a,N*sizeof(float));
cudaMalloc((void**)&d_b,N*sizeof(float));
cudaMalloc((void**)&d_c,N*sizeof(float));
cudaMemcpy(d_a, a ,N*sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(d_b, b ,N*sizeof(float), cudaMemcpyHostToDevice);
cudaProfilerStart();
for(int k = 0; k < 100; k++)
{
k_dummykernel1<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel2<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel3<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel4<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel1<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel2<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel3<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel4<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
for(int i = 0; i < N2; i++)
{
c[i] = pow(a[i],b[i]);
}
}
cudaDeviceSynchronize();
usleep(40000);
for(int k = 0; k <= 100; k++)
{
#pragma omp parallel sections num_threads(2)
{
#pragma omp section
{
k_dummykernel1<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel2<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel3<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel4<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel1<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel2<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel3<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel4<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
}
#pragma omp section
{
for(int i = 0; i < N2; i++)
{
c[i] = pow(a[i],b[i]);
}
}
}
}
cudaDeviceSynchronize();
cudaProfilerStop();
delete[] a;
delete[] b;
delete[] c;
cudaFree((void*)d_a);
cudaFree((void*)d_b);
cudaFree((void*)d_c);
}
#包括
#包括
#包括
#根据块64定义线程
__全局无效k_dummykernel1(常量浮点*a,常量浮点*b,浮点*c,常量整数N)
{
const int id=blockIdx.x*blockDim.x+threadIdx.x;
if(id 对于(int k=0;k我没有得到像您这样极端的结果,因此我不确定这是否真的会对您有所帮助。我看到第二个线程的API调用速度较慢,因此确保只有一个线程处理所有CUDA API调用会在一定程度上改善结果。这通常是一个好主意,并且您可以看到,在小节中没有这种情况。简单的方法是:
#pragma omp parallel num_threads(2)
{
for(int k = 0; k <= KMAX; k++)
{
if (omp_get_thread_num() == 0)
{
k_dummykernel1<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel2<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel3<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel4<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel1<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel2<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel3<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
k_dummykernel4<<<(N + THREADS_PER_BLOCK - 1)/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(d_a,d_b,d_c,N);
}
else
{
for(int i = 0; i < N2; i++)
{
c[i] = pow(a[i],b[i]);
}
}
// this makes sure that the behavior is consistent
#pragma omp barrier
}
}
使用nvprof
我得到:
Serial time: 0.058805
Parallel time (pinned thread): 0.054116
Parallel time (sections): 0.053535
因此,基本上你必须用大量的盐从可视化分析器中获取结果。从详细跟踪中获得的洞察力通常非常有用,但在这种情况下,你应该依赖于端到端的测量。我已经运行了你的代码,并在可视化分析器中查看了它。我看到的与你看到的相反。kerne的束在VisualProfiler中,左侧的内核启动更分散,在时间轴上花费的时间更长,右侧的内核启动束更紧密,在时间轴上花费的时间更少。这是在CUDA 9上。2@RobertCrovella我也在使用CUDA 9.2。您是否使用了其他编译器开关?或者您是否修改了一些探查器中的设置?我在Ubuntu 18.04上运行这段代码,GPU:GTX 1080 TI,CPU:Intel Xeon W-2133Thanks!它实际上有助于将k迭代次数增加到2000次。这有点奇怪,但还好,只要基本思想可行,我就可以使用它。如果你能再回答我两个问题就太好了:1.在你的示例代码中,可能吗简单地在CPU-i循环前面放一个“#pragma omp parallel for”,进一步并行化这个部分?或者我必须增加外部pragma中的num#u threads(…)?2.如何测量计时?我总是使用std::chrono,并在第二次迭代开始时获取开始时间戳(因为有人告诉我,第一个总是慢得多)和cudaDeviceSyncronize()之后的结束时间戳不幸的是,没有,您需要在团队的所有线程中遇到for循环,但至少有一个线程正在处理内核。如果您想进一步并行化内部循环,可能需要嵌套的并行区域,这可能会变得混乱。或者您可以在考虑到少一个线程的情况下对循环应用手动工作共享。2)我在k循环中使用了clock\u gettime
(不包括计时中的cudaDeviceSyncronize
)。
Serial time: 0.058805
Parallel time (pinned thread): 0.054116
Parallel time (sections): 0.053535