我有这个简单的代码如下所示,只是使用流将一些数据从主机复制到设备。但是在运行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卡。
答案 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>