同步多个Cuda流

时间:2016-07-18 20:15:53

标签: c++ cuda

对于我目前正在开发的应用程序,我希望有一个长内核(也就是说,相对于其他内核需要很长时间才能完成的内核)与多个较短内核的序列同时执行同时运行。然而,更复杂的是,四个较短的内核在完成之后需要同步,以便执行另一个短内核来收集和处理其他短内核的数据输出。

以下是我的想法的示意图,编号的绿色条表示不同的内核:

Schematic of what I have in mind.

为了实现这一点,我编写了类似于以下内容的代码:

// definitions of kernels 1-6

class Calc
{
    Calc()
    {
        // ...
        cudaStream_t stream[5];
        for(int i=0; i<5; i++) cudaStreamCreate(&stream[i]);
        // ...
    }

    ~Calc()
    {
        // ...
        for(int i=0; i<5; i++) cudaStreamDestroy(stream[i]);
        // ...
    }

    void compute()
    {
        kernel1<<<32, 32, 0, stream[0]>>>(...);
        for(int i=0; i<20; i++) // this 20 is a constant throughout the program
        {
            kernel2<<<1, 32, 0, stream[1]>>>(...);
            kernel3<<<1, 32, 0, stream[2]>>>(...);
            kernel4<<<1, 32, 0, stream[3]>>>(...);
            kernel5<<<1, 32, 0, stream[4]>>>(...);
            // ?? synchronisation ??
            kernel6<<<1, 32, 0, stream[1]>>>(...);
        }
    }
}

int main()
{
    // preparation

    Calc C;

    // run compute-heavy function as many times as needed
    for(int i=0; i<100; i++)
    {
        C.compute();
    }

    // ...

    return 0;
}

注意:块,线程和共享内存的数量只是任意数字。

现在,我将如何在每次迭代中正确同步内核2-5?首先,我不知道哪个内核需要花费最长的时间才能完成,因为这可能取决于用户输入。此外,我已尝试使用cudaDeviceSynchronize()cudaStreamSynchronize(),但这些时间超过总执行时间的三倍。

Cuda事件或许可以走了吗?如果是这样,我该如何应用它们?如果没有,那么这样做的正确方法是什么?

非常感谢。

1 个答案:

答案 0 :(得分:2)

首先需要提出两条评论。

  1. 启动小内核(一个块)通常不是从GPU中获得良好性能的方法。同样,每个块具有少量线程的内核(32)通常会施加占用限制,这将阻止GPU的完全性能。启动多个并发内核并不能减轻这种第二个考虑因素。我不会再花时间在这里,因为你已经说过这些数字是随意的(但请看下面的下一条评论)。

  2. 目睹实际的内核并发很难。我们需要内核具有相对较长的执行时间,但对GPU资源的需求相对较低。 <<<32,32>>>的内核可能填充您正在运行的GPU,从而阻止并发内核的块运行。

  3. 您的问题似乎归结为&#34;如何阻止kernel6开始直到kernel2-5完成。

    可以为此使用事件。基本上,在kernel2-5启动后,你会record an event进入每个流,然后你会发出一个cudaStreamWaitEvent调用,一个用于4个事件中的每一个,之前到启动kernel6

    像这样:

            kernel2<<<1, 32, 0, stream[1]>>>(...);
            cudaEventRecord(event1, stream[1]);
            kernel3<<<1, 32, 0, stream[2]>>>(...);
            cudaEventRecord(event2, stream[2]);
            kernel4<<<1, 32, 0, stream[3]>>>(...);
            cudaEventRecord(event3, stream[3]);
            kernel5<<<1, 32, 0, stream[4]>>>(...);
            cudaEventRecord(event4, stream[4]);
            // ?? synchronisation ??
            cudaStreamWaitEvent(stream[1], event1);
            cudaStreamWaitEvent(stream[1], event2);
            cudaStreamWaitEvent(stream[1], event3);
            cudaStreamWaitEvent(stream[1], event4);
            kernel6<<<1, 32, 0, stream[1]>>>(...);
    

    请注意,以上所有调用都是异步。它们都不应该花费超过几微秒来处理,并且它们都不会阻止CPU线程继续运行,这与使用cudaDeviceSynchronize()cudaStreamSynchronize()不同,后者通常阻止CPU线程。

    因此,在循环中执行上述序列(例如cudaStreamSynchronize(stream[1]);)之后,您可能需要某种同步,否则所有这些的异步性质将会变得毛茸茸( ,根据你的原理图,似乎你可能不希望迭代i + 1的kernel2-5开始直到迭代的内核i完成?)注意我已经遗漏了事件创建,也许还有其他为此,我假设您可以弄清楚或参考任何使用事件的示例代码,或参考文档。

    即使你实现了所有这些基础设施,你见证(或不见)实际内核并发的能力将由你的内核本身决定,我在这个答案中建议的任何内容。所以如果你回来说'#34;我这样做了,但我的内核并没有同时运行&#34;这实际上是一个与你所提出的问题不同的问题,在这里,我会把你推荐给我的评论#2。