【发布时间】:2022-01-01 10:39:36
【问题描述】:
我有两个按顺序处理一些数据的内核(仅使用一个线程启动)。我想将两者结合起来,这样我就可以使用两个线程来启动一个内核。这样做之后,我期望得到一个 max(kernel1, kernel2) 的执行时间,但我得到的是两个执行时间的总和。我将问题缩小到类似于下面的代码。
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include<iostream>
#include<string>
#include<vector>
#include<random>
#include<functional>
#include<algorithm>
#include<iterator>
__global__ void dummyKernel(const float *d_data_Re, const float *d_data_Im,
float *d_out_Re, float *d_out_Im, const int dataLen) {
int i{ threadIdx.x };
if (i == 0) {
printf("Thread zero started \n");
for (int j{}; j < 1000000; j++)
d_out_Re[j%dataLen] = sqrtf(2) + d_data_Re[j%dataLen] * (j % 4 == 1);
printf("Thread zero finished \n");
}
else if (i == 1) {
printf("Thread one started \n");
for (int j{}; j < 1000000; j++)
d_out_Im[j%dataLen] = sqrtf(2) + d_data_Im[j%dataLen] * (j % 4 == 1);
printf("Thread one finished \n");
}
}
__global__ void dummyKernel2(const float *d_data_Re, const float *d_data_Im,
float *d_out_Re, float *d_out_Im, const int dataLen) {
int i{ threadIdx.x };
//if (i == 0) {
printf("Thread zero started \n");
for (int j{}; j < 1000000; j++)
d_out_Re[j%dataLen] = sqrtf(2) + d_data_Re[j%dataLen] * (j % 4 == 1);
printf("Thread zero finished \n");
//}
//else if (i == 1) {
// printf("Thread one started \n");
// for (int j{}; j < 1000000; j++)
// d_out_Im[j%dataLen] = sqrtf(2) + d_data_Im[j%dataLen] * (j % 4 == 1);
// printf("Thread one finished \n");
//}
}
int main()
{
cudaError_t cudaStatus = cudaSetDevice(0);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaSetDevice failed! Do you have a CUDA-capable GPU installed?");
return 1;
}
const int sizeOfFrame = 2 * 1024 * 1024;
std::vector<float> data_re(sizeOfFrame), data_im;
//random number generator
std::uniform_real_distribution<float> distribution(0.0f, 2.0f); //Values between 0 and 2
std::mt19937 engine; // Mersenne twister MT19937
auto generator = std::bind(distribution, engine);
std::generate_n(data_re.begin(), sizeOfFrame, generator);
std::copy(data_re.begin(), data_re.end(), std::back_inserter(data_im));
//
float *d_data_re, *d_data_im;
cudaMalloc(&d_data_re, sizeOfFrame * sizeof(float));
cudaMalloc(&d_data_im, sizeOfFrame * sizeof(float));
cudaMemcpy(d_data_re, data_re.data(), sizeOfFrame * sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(d_data_im, data_im.data(), sizeOfFrame * sizeof(float), cudaMemcpyHostToDevice);
float *d_pll_out_re, *d_pll_out_im;
cudaMalloc(&d_pll_out_re, sizeOfFrame * sizeof(float));
cudaMalloc(&d_pll_out_im, sizeOfFrame * sizeof(float));
dummyKernel << <1, 2 >> >(d_data_re, d_data_im,
d_pll_out_re, d_pll_out_im, sizeOfFrame);
cudaDeviceSynchronize();
// cudaDeviceReset must be called before exiting in order for profiling and
// tracing tools such as Nsight and Visual Profiler to show complete traces.
cudaStatus = cudaDeviceReset();
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaDeviceReset failed!");
return 1;
}
return 0;
}
顺便说一句,我从this 问题的答案中获得了随机数生成器的代码。所以, dummyKernel 没有做任何有用的事情,我只是想要一个需要相对较长时间才能完成的内核。如果启动 dummyKernel,输出的顺序将是“线程零开始”、“线程零完成”、“线程一开始”、“线程一完成”。顺序的。但是如果你启动 dummyKernel2,输出的顺序将是“线程零启动”、“线程零启动”、“线程零完成”、“线程零完成”,执行时间几乎是 dummyKernel 的一半。我不理解这种行为以及我使用的 if-else 的效果。 操作系统:Windows 10、GTX 1050 Ti、CUDA 驱动程序/运行时版本:11.1/10.1。
【问题讨论】:
-
所有条件分支都在warp中串行执行。这在 GPU(更具体地说是 SIMT 模型)上是预期的。请阅读:stackoverflow.com/questions/11687500/…(可能还有 CUDA 手册)。
-
因此尝试启动 33 个线程 (0..32),让线程 1-31 什么都不做,并使用线程 0 和 32 来查看它是否进行了更改(仅用于测试/教育目的 -这不是一个好的并行设置)
-
@JérômeRichard 谢谢,现在开始有意义了
-
@Sebastian 谢谢,经过测试,它解决了我的问题。
-
在比你的 (Pascal) 更新的 Nvidia GPU (>= Volta) 中,warp 的每个线程可能执行不同的程序代码位置(这称为独立线程调度),但让warp 的线程不会发散,尤其是对于合并的内存访问。并且不能保证 Nvidia 不会再次为未来的架构恢复行为以节省芯片面积或例如将有 8 个子组,每个子组 4 个线程,它们不能发散。
标签: cuda