cudamemcpyasync和流行为理解

时间:2013-09-19 11:33:01

标签: cuda nvidia

我有这个简单的代码如下所示,只是使用流将一些数据从主机复制到设备。但是在运行nvprof之后我很困惑,因为cudamemcpyasync真的是异步并理解了这些流。

#include <stdio.h>

#define NUM_STREAMS 4
cudaError_t memcpyUsingStreams (float           *fDest,
                                float           *fSrc,
                                int             iBytes,
                                cudaMemcpyKind  eDirection,
                                cudaStream_t    *pCuStream)
{
    int             iIndex = 0 ;
    cudaError_t     cuError = cudaSuccess ;
    int             iOffset = 0 ;

    iOffset = (iBytes / NUM_STREAMS) ;
    /*Creating streams if not present */
    if (NULL == pCuStream)
    {
            pCuStream = (cudaStream_t *) malloc(NUM_STREAMS * sizeof(cudaStream_t));
            for (iIndex = 0 ; iIndex < NUM_STREAMS; iIndex++)
            {
                    cuError = cudaStreamCreate (&pCuStream[iIndex]) ;
            }
    }

    if (cuError != cudaSuccess)
    {
            cuError = cudaMemcpy (fDest, fSrc, iBytes, eDirection) ;
    }
    else
    {
            for (iIndex = 0 ; iIndex < NUM_STREAMS; iIndex++)
            {
                    iOffset = iIndex * iOffset ;
                    cuError = cudaMemcpyAsync (fDest +  iOffset , fSrc + iOffset, iBytes / NUM_STREAMS , eDirection, pCuStream[iIndex]) ;
            }
    }

    if (NULL != pCuStream)
    {
            for (iIndex = 0 ; iIndex < NUM_STREAMS; iIndex++)
            {
                    cuError = cudaStreamDestroy (pCuStream[iIndex]) ;
            }
            free (pCuStream) ;
    }
    return cuError ;
}


int main()
{
    float *hdata = NULL ;
    float *ddata = NULL ;
    int i, j, k, index ;
    cudaStream_t *abc = NULL ;

    hdata = (float *) malloc (sizeof (float) * 256 * 256 * 256) ;

    cudaMalloc ((void **) &ddata, sizeof (float) * 256 * 256 * 256) ;

    for (i=0 ; i< 256 ; i++)
    {
        for (j=0; j< 256; j++)
        {
            for (k=0; k< 256 ; k++)
            {
                index = (((i * 256) + j) * 256) + k;
                hdata [index] = index ;
            }
        }
    }

    memcpyUsingStreams (ddata, hdata, sizeof (float) * 256 * 256 * 256,  cudaMemcpyHostToDevice, abc) ;

    cudaFree (ddata) ;
    free (hdata) ;

    return 0;
}

nvprof结果如下。

    Start  Duration           Grid Size     Block Size     Regs*    SSMem*    DSMem*      Size  Throughput    Device   Context    Stream  Name
 104.35ms   10.38ms                   -              -         -         -         -   16.78MB    1.62GB/s         0         1         7  [CUDA memcpy HtoD]
 114.73ms   10.41ms                   -              -         -         -         -   16.78MB    1.61GB/s         0         1         8  [CUDA memcpy HtoD]
 125.14ms   10.46ms                   -              -         -         -         -   16.78MB    1.60GB/s         0         1         9  [CUDA memcpy HtoD]
 135.61ms   10.39ms                   -              -         -         -         -   16.78MB    1.61GB/s         0         1        10  [CUDA memcpy HtoD]

因为开始时间,所以我不明白在这里使用流的意义。它看起来是顺序的。请帮助我理解我在这里做错了什么。我正在使用特斯拉K20c卡。

1 个答案:

答案 0 :(得分:1)

将GPU连接到系统的PCI Express链接只有一个通道进入卡,一个通道来自卡。这意味着最多可以有一个cudaMemcpy(Async)操作,它在每个方向的任何给定时间实际执行(即最多一个DtoH和一个HtoD)。所有其他cudaMemcpy(Async)操作将排队,等待提前完成。

您不能同时在同一方向上进行两次操作。每个方向一次一个。

正如@JackOLantern所述,流的主要好处是重叠memcopies和compute,或者允许多个内核同时执行。它还allows one DtoH copy to run concurrently with one HtoD copy

由于您的程序执行所有HtoD副本,因此它们都会以串行方式执行。每个副本都必须等待它之前的副本完成。

即使同时执行HtoD和DtoH memcopy,也需要具有多个复制引擎的设备;您可以使用deviceQuery发现有关您设备的信息。

我还应该指出,要启用并发行为,您应该使用cudaHostAlloc而不是malloc作为主机端缓冲区。

编辑:上面的答案中包含的GPU最多有2个复制引擎(每个方向一个),并且对于此类GPU仍然是正确的。然而,存在一些具有2个以上复制引擎的较新的Pascal和Volta家族成员GPU。在这种情况下,每个方向有2个(或更多)复制引擎,理论上可以在该方向上“在飞行中”进行2次(或更多次)传输。但是,这不会改变PCIE(或NVLink)总线本身的特性。您仍然受限于可用带宽,并且确切的低级别行为(无论此类传输似乎是“序列化”还是看起来同时运行,但由于共享带宽需要更长时间)在大多数情况下都不应该太重要。 / p>