执行C ++ CUDA内核时出现黑屏,输出正确的结果

时间:2019-03-31 12:40:05

标签: c++ cuda gpu

我正在使用CUDA C ++在数组上执行一些简单的并行计算。一切正常,内核输出正确的结果(已通过串行CPU代码检查),但是在内核执行期间,在内核执行的整个过程中,我的整个屏幕都变黑了。我是CUDA的新手,所以我可能做错了什么,我似乎无法弄清楚是什么。

#define KERNEL_FOR_ITERS 1e6

__global__ void addKernel(float *c, const float *a, const float *b)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i % 2 == 0)
        for (int j = 0; j < KERNEL_FOR_ITERS; j++)
            c[i] += sqrt(abs(sin(a[i] * b[i])));
    else
        for (int j = 0; j < KERNEL_FOR_ITERS; j++)
            c[i] += sqrt(abs(cos(a[i] * b[i])));
}

我的问题是,在不影响速度的前提下,我可以在整个内核执行期间阻止屏幕变黑吗?

1 个答案:

答案 0 :(得分:2)

如果您描述您的设置,包括正在运行的操作系统和GPU,GPU是否在驱动显示器,以及在Windows操作系统中,GPU处于WDDM或TCC模式,这将很有用。 / p>

但是,我们可以不做一些一般性声明。

如评论中所指出,当前,运行CUDA内核的GPU将不支持显示请求(如果它也支持显示)。这意味着在GPU内核运行时,显示器似乎“冻结”或可能变黑。当然,将来可能会改变,但这是当前和预期的行为。

在这种情况下,通常的建议是,如果您根本不想打扰显示器,则使用第二个GPU来运行CUDA,如果您在Windows上,则最好使用该GPU并放置在TCC模式。

为减轻仅使用单个GPU时的影响,并确实在单个GPU显示环境中为生产目的提供CUDA支持,重要的是,应用程序的CUDA端的设计应确保内核持续时间是有限的。对于良好的交互性,合理的出发点是将内核持续时间限制为0.1秒或更短,因为交互性的损失程度可能不会特别明显。如果您或其他人不同意该人为因素声明,则可以;我们不必为此争论。将内核持续时间减少到您决定的任何水平,都会带来良好的显示交互性。

在Windows情况下(据我所知,在Linux情况下),通过WDDM命令批处理,情况变得更加复杂。为了提高性能,可以对命令进行批处理,并且背对背内核调用的批处理可能会导致感知到的交互性损失更长的时间,而不仅仅是单个内核调用所指示的。我没有任何方法可以正式解决此问题。在每次内核调用之后,您可以通过发出伪造的(即不是必须的)CUDA操作(例如cudaStreamQuery())来“刷新” WDDM命令队列。再说一次,我不知道正式记录的方法,在某种程度上,它可能取决于您的应用程序设计。

在性能方面,CUDA内核启动通常需要大约100微秒或更短的启动开销(我们可以将其称为浪费时间)。因此,如果将长时间运行的内核分解为100毫秒的“块”,并且每个块都增加了约100微秒的开销,则对性能的净影响可能约为CUDA计算吞吐量降低0.1%(假设显示任务很简单)。

以您提供的代码为例,您希望将该内核分解为一系列内核,在您选择的GPU上对其进行基准测试/定时,以便内核运行时间不超过约100毫秒(或您选择的数量)。

#define KERNEL_FOR_ITERS 1e6


__global__ void addKernel(float *c, const float *a, const float *b,const int iters)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i % 2 == 0)
        for (int j = 0; j < iters; j++)
            c[i] += sqrt(abs(sin(a[i] * b[i])));
    else
        for (int j = 0; j < iters; j++)
            c[i] += sqrt(abs(cos(a[i] * b[i])));
}

...
const int loop_iters = 1e4; // chosen by tuning or benchmarking
cudaStream_t str;
cudaStreamCreate(&str);
for (int i = 0; i < KERNEL_FOR_ITERS; i+= loop_iters){
  addKernel<<<...,0,str>>>(d_c, d_a, d_b, loop_iters);
  cudaStreamQuery(str);//probably unnecessary on linux}

我不认为这是您实际使用的内核,但是,顺便说一句,可以通过将线程之间实际上不同的内容限制为一小段代码来改善其性能特征。例如:

__global__ void addKernel(float *c, const float *a, const float *b,const int iters)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    float val = a[i] * b[i];
    if (i % 2 == 0)
       val = sin(val);
    else 
       val = cos(val);
    for (int j = 0; j < iters; j++)
            c[i] += sqrt(abs(val));
}

无论如何,编译器可能会发现这种收缩,但是我通常会尽力为它提供最佳的“领先”。