C++ 同步多个Cuda流

C++ 同步多个Cuda流,c++,cuda,C++,Cuda,对于我目前正在开发的应用程序,我希望有一个长内核(也就是说,一个相对于其他内核需要很长时间才能完成的内核)与同时运行的多个较短内核序列并行执行。然而,更复杂的是,四个较短的内核在完成后都需要同步,以便执行另一个较短的内核来收集和处理其他较短内核输出的数据 下面是我想到的示意图,带编号的绿色条表示不同的内核: 为了实现这一点,我编写了如下代码: // definitions of kernels 1-6 class Calc { Calc() { // ...

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

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

为了实现这一点,我编写了如下代码:

// 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;
}
//内核1-6的定义
类计算
{
Calc()
{
// ...
cudaStream_t stream[5];

对于(int i=0;i有两个注释需要首先作出

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

  • 很难看到实际的内核并发。我们需要执行时间相对较长但对GPU资源要求相对较低的内核。
    的内核可能会填满您正在运行的GPU,从而阻止并发内核中的块运行

  • 您的问题似乎归结为“如何防止
    kernel6
    启动,直到
    kernel2-5
    完成

    可以使用事件来实现这一点。基本上,在内核2-5启动后,您将进入每个流,并在
    kernel6
    启动之前,为4个事件中的每一个发出一个调用

    像这样:

            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]>>>(...);
    
    kernel2(…);
    cudaEventRecord(事件1,流[1]);
    核3(…);
    cudaEventRecord(事件2,流[2]);
    核4(…);
    cudaEventRecord(事件3,流[3]);
    核5(…);
    cudaEventRecord(事件4,流[4]);
    //?同步??
    cudaStreamWaitEvent(流[1],事件1);
    cudaStreamWaitEvent(流[1],事件2);
    cudaStreamWaitEvent(流[1],事件3);
    cudaStreamWaitEvent(流[1],事件4);
    核仁6(…);
    
    请注意,以上所有调用都是异步的。处理这些调用的时间都不应超过几微秒,而且它们都不会阻止CPU线程继续,这与使用
    cudaDeviceSynchronize()
    cudaStreamSynchronize()
    通常会阻止CPU线程不同

    因此,在循环中执行上述序列(例如,
    cudaStreamSynchronize(stream[1]);
    )之后,您可能需要某种类型的同步,否则所有这些的异步性质将变得难以理解(另外,根据您的示意图,在迭代i+1的内核6完成之前,您可能不希望迭代i+1的内核2-5开始?)请注意,我遗漏了事件创建和其他示例,我假设您可以了解这一点,或者参考使用事件的任何示例代码,或者参考文档


    即使您实现了所有这些基础设施,您见证(或不见证)实际内核并发性的能力也将由您的内核本身决定,而不是我在回答中提出的任何建议“这实际上是一个不同于您在此处提出的问题,我想让您先看看我上面的评论#2。

    非常感谢。这肯定是解决问题的有效方法。但是,我替换了
    cudaStreamSynchronize(stream[1]);
    流1记录了另一个事件,然后每个流都记录了一个
    cudaStreamWaitEvent(…);
    。这给出了完全相同的结果,但运行速度快了约2.5倍。