从 GPU 复制到 CPU 比从 CPU 复制到 GPU 慢

Posted

技术标签:

【中文标题】从 GPU 复制到 CPU 比从 CPU 复制到 GPU 慢【英文标题】:copy from GPU to CPU is slower than copying CPU to GPU 【发布时间】:2012-10-31 17:36:52 【问题描述】:

我开始学习cuda有一段时间了,遇到以下问题

在下面看看我的表现:

复制 GPU

int* B;
// ...
int *dev_B;    
//initialize B=0

cudaMalloc((void**)&dev_B, Nel*Nface*sizeof(int));
cudaMemcpy(dev_B, B, Nel*Nface*sizeof(int),cudaMemcpyHostToDevice);
//...

//Execute on GPU the following function which is supposed to fill in 
//the dev_B matrix with integers


findNeiborElem <<< Nblocks, Nthreads >>>(dev_B, dev_MSH, dev_Nel, dev_Npel, dev_Nface, dev_FC);

再次复制 CPU

cudaMemcpy(B, dev_B, Nel*Nface*sizeof(int),cudaMemcpyDeviceToHost);
    将数组 B 复制到 dev_B 只需几分之一秒。 但是将数组 dev_B 复制回 B 需要很长时间。

    findNeiborElem 函数涉及每个线程的循环 例如好像是这样的

    __ global __ void findNeiborElem(int *dev_B, int *dev_MSH, int *dev_Nel, int *dev_Npel, int *dev_Nface, int *dev_FC)
    
        int tid=threadIdx.x + blockIdx.x * blockDim.x;
        while (tid<dev_Nel[0])
            for (int j=1;j<=Nel;j++)
                 // do some calculations
                 B[ind(tid,1,Nel)]=j// j in most cases do no go all the way to the Nel reach
                 break; 
            
        tid += blockDim.x * gridDim.x; 
        
    
    

它非常奇怪的是,将 dev_B 复制到 B 的时间与 j 索引的迭代次数成正比。

例如,如果Nel=5,则时间约为5 sec

当我增加Nel=20 时,时间大约是20 sec

我希望复制时间应该独立于需要分配矩阵dev_B 的值的内部迭代。

我还希望从 CPU 到 CPU 复制相同矩阵的时间将是相同的顺序。

你知道出了什么问题吗?

【问题讨论】:

【参考方案1】:

你应该使用事件而不是使用clock()来测量时间:

有了事件,你会得到这样的东西:

  cudaEvent_t start, stop;   // variables that holds 2 events 
  float time;                // Variable that will hold the time
  cudaEventCreate(&start);   // creating the event 1
  cudaEventCreate(&stop);    // creating the event 2
  cudaEventRecord(start, 0); // start measuring  the time

  // What you want to measure
  cudaMalloc((void**)&dev_B, Nel*Nface*sizeof(int));
  cudaMemcpy(dev_B, B, Nel*Nface*sizeof(int),cudaMemcpyHostToDevice);

  cudaEventRecord(stop, 0);                  // Stop time measuring
  cudaEventSynchronize(stop);               // Wait until the completion of all device 
                                            // work preceding the most recent call to cudaEventRecord()

  cudaEventElapsedTime(&time, start, stop); // Saving the time measured

编辑:附加信息:

“内核启动在完成之前将控制权返回给 CPU 线程。因此,您的计时结构正在测量内核执行时间以及第二个 memcpy。当在内核之后计时复制时,您的计时器代码正在执行立即,但 cudaMemcpy 正在等待内核在启动之前完成。这也解释了为什么您的数据返回时间测量似乎因内核循环迭代而异。它还解释了为什么花在内核函数上的时间“可以忽略不计” “”。归功于Robert Crovella

【讨论】:

是的,您应该使用这种方法。内核启动在完成之前将控制权返回给 CPU 线程。因此,您的计时结构正在测量内核执行时间以及第二个 memcpy。在内核之后计时复制时,您的计时器代码将立即执行,但 cudaMemcpy 正在等待内核在启动之前完成。这也解释了为什么您的数据返回时间测量似乎因内核循环迭代而异。它还解释了为什么花在内核函数上的时间“可以忽略不计”。【参考方案2】:

关于你的第二个问题

 B[ind(tid,1,Nel)]=j// j in most cases do no go all the way to the Nel reach

在 GPU 上执行计算时,由于同步的原因,每个完成工作的线程都不会执行任何计算,直到同一个工作组中的所有线程都完成。

换句话说,您需要执行此计算的时间将是最坏情况下的时间,如果 大多数 线程没有一直下降也没关系。

我不确定你的第一个问题,你如何测量时间?我对 cuda 不太熟悉,但我认为当从 CPU 复制到 GPU 时,实现会缓冲您的数据,隐藏所涉及的有效时间。

【讨论】:

感谢您的回答,但让我澄清一下。我纯粹是指在复制程序上花费的时间。花在函数“findNeiborElem”上的时间几乎可以忽略不计。为了测量我使用 start = std::clock(); 的时间cudaMemcpy(B, dev_B, NelNfacesizeof(int),cudaMemcpyDeviceToHost);持续时间 = ( std::clock() - start ) / (double) CLOCKS_PER_SEC; std::cout 这取决于您实际要测量的时间,请记住,大多数操作都是异步执行的。换句话说,当程序返回时,GPU 几乎没有完成任何工作,除非程序发出同步点(cudaMemcpyDeviceToHost 就是这种情况)。这里***.com/questions/3553843/… 的解释比我能解释的更好。 那,数据路径 GPU->CPU 的性能在 CUDA 之前一直是无关紧要的,所以它可能没有那么彻底优化(AGP 甚至有意识地决定传输速度应该是不对称的)。

以上是关于从 GPU 复制到 CPU 比从 CPU 复制到 GPU 慢的主要内容,如果未能解决你的问题,请参考以下文章

有没有更好更快的方法来使用推力从 CPU 内存复制到 GPU?

将 2D 纹理阵列的单层从 GPU 复制到 CPU

将Pytorch模型从CPU转换成GPU

如何检查keras tensorflow后端是GPU还是CPU版本? [复制]

只有使用 cupy 的 GPU 到 CPU 传输速度非常慢

在 CPU 和 GPU 之间同步静态分配的结构实例