如何在多个内核启动之间同步全局内存?

时间:2011-07-01 09:21:27

标签: cuda

我想在FOR LOOP(伪)中多次启动以下内核:

 __global__ void kernel(t_dev is input array in global mem) {

    __shared__ PREC tt[BLOCK_DIM];

    if (thid < m) {
       tt[thid] = t_dev.data[ii];          // MEM READ!
    }

    ... // MODIFY 

    __syncthreads();

    if (thid < m) {
        t_dev.data[thid] = tt[thid];    // MEM WRITE!
    }

    __threadfence(); // or __syncthreads();  //// NECESSARY!! but why?

}

我在概念上做的是从t_dev读取值。修改它们,然后再写出全局内存!然后我再次启动相同的内核!!

为什么我显然需要_threadfence或__syncthread 否则结果会出错,因为当同一内核再次启动时,内存写入不会完成。多数民众赞成在这里发生了什么,我的GTX580启用了设备重叠,

但是为什么在下一个内核启动时没有完成全局mem写入...这是因为设备重叠还是因为它总是这样?我想,当我们在内核之后启动内核时,mem write / reads在一个内核之后完成......: - )

感谢您的回答!

一些代码:

 for(int kernelAIdx = 0; kernelAIdx < loops; kernelAIdx++){

      proxGPU::sorProxContactOrdered_1threads_StepA_kernelWrap<PREC,SorProxSettings1>(
              mu_dev,x_new_dev,T_dev,x_old_dev,d_dev,
              t_dev,
              kernelAIdx,
              pConvergedFlag_dev,
              m_absTOL,m_relTOL);


      proxGPU::sorProx_StepB_kernelWrap<PREC,SorProxSettings1>(
              t_dev,
              T_dev,
              x_new_dev,
              kernelAIdx
              );

        }

这些是循环中的两个内核,t_dev和x_new_dev,从步骤A移到步骤B,

内核A看起来如下:

 template<typename PREC, int THREADS_PER_BLOCK, int BLOCK_DIM, int PROX_PACKAGES, typename TConvexSet>
 __global__ void sorProxContactOrdered_1threads_StepA_kernel( 
  utilCuda::Matrix<PREC> mu_dev,
  utilCuda::Matrix<PREC> y_dev,
  utilCuda::Matrix<PREC> T_dev,  
  utilCuda::Matrix<PREC> x_old_dev,
  utilCuda::Matrix<PREC> d_dev, 
  utilCuda::Matrix<PREC> t_dev, 
  int kernelAIdx,
  int maxNContacts, 
  bool * convergedFlag_dev, 
  PREC _absTOL, PREC _relTOL){

 //__threadfence() HERE OR AT THE END; THEN IT WORKS???? WHY

 // Assumend 1 Block, with THREADS_PER_BLOCK Threads and Column Major Matrix T_dev 

     int thid = threadIdx.x;
     int m = min(maxNContacts*PROX_PACKAGE_SIZE, BLOCK_DIM); // this is the actual size of the diagonal block!
     int i = kernelAIdx * BLOCK_DIM;
     int ii = i + thid;

     //First copy x_old_dev in shared
     __shared__ PREC xx[BLOCK_DIM]; // each thread writes one element, if its in the limit!!
     __shared__ PREC tt[BLOCK_DIM];

     if(thid < m){
        xx[thid] = x_old_dev.data[ii];
        tt[thid] = t_dev.data[ii];
     }
     __syncthreads();


     PREC absTOL = _absTOL;
     PREC relTOL = _relTOL;

     int jj;
     //PREC T_iijj;
     //Offset the T_dev_ptr to the start of the Block
     PREC * T_dev_ptr  = PtrElem_ColM(T_dev,i,i);
     PREC * mu_dev_ptr = &mu_dev.data[PROX_PACKAGES*kernelAIdx];
     __syncthreads();
     for(int j_t = 0; j_t < m ; j_t+=PROX_PACKAGE_SIZE){

        //Select the number of threads we need!

        // Here we process one [m x PROX_PACKAGE_SIZE] Block

        // First  Normal Direction ==========================================================
        jj =  i  +  j_t;
        __syncthreads();

        if( ii == jj ){ // select thread on the diagonal ...

           PREC x_new_n = (d_dev.data[ii] + tt[thid]);

           //Prox Normal! 
           if(x_new_n <= 0.0){
              x_new_n = 0.0;
           }
          /* if( !checkConverged(x_new,xx[thid],absTOL,relTOL)){
              *convergedFlag_dev = 0;
           }*/

           xx[thid] = x_new_n;
           tt[thid] = 0.0;
        }
        // all threads not on the diagonal fall into this sync!
        __syncthreads();


        // Select only m threads!
        if(thid < m){
           tt[thid] += T_dev_ptr[thid] * xx[j_t];
        }
        // ====================================================================================


        // wee need to syncronize here because one threads finished lambda_t2 with shared mem tt, which is updated from another thread!
        __syncthreads();



         // Second  Tangential Direction ==========================================================
        jj++;
        __syncthreads();
        if( ii == jj ){ // select thread on diagonal, one thread finishs T1 and T2 directions.

           // Prox tangential
           PREC lambda_T1 =  (d_dev.data[ii] + tt[thid]);
           PREC lambda_T2 =  (d_dev.data[ii+1] + tt[thid+1]);
           PREC radius = (*mu_dev_ptr) * xx[thid-1];
           PREC absvalue = sqrt(lambda_T1*lambda_T1 + lambda_T2*lambda_T2);

           if(absvalue > radius){
              lambda_T1   =  (lambda_T1  * radius ) / absvalue;
              lambda_T2   =  (lambda_T2  * radius ) / absvalue;
           }


           /*if( !checkConverged(lambda_T1,xx[thid],absTOL,relTOL)){
              *convergedFlag_dev = 0;
           }

           if( !checkConverged(lambda_T2,xx[thid+1],absTOL,relTOL)){
              *convergedFlag_dev = 0;
           }*/

           //Write the two values back!
           xx[thid] = lambda_T1;
           tt[thid] = 0.0;
           xx[thid+1] = lambda_T2;
           tt[thid+1] = 0.0;
        }

        // all threads not on the diagonal fall into this sync!
        __syncthreads();


        T_dev_ptr = PtrColOffset_ColM(T_dev_ptr,1,T_dev.outerStrideBytes);
        __syncthreads();
        if(thid < m){
           tt[thid] += T_dev_ptr[thid] * xx[j_t+1];
        }
        __syncthreads();
        T_dev_ptr = PtrColOffset_ColM(T_dev_ptr,1,T_dev.outerStrideBytes);
        __syncthreads();
        if(thid < m){
           tt[thid] += T_dev_ptr[thid] * xx[j_t+2];
        }
        // ====================================================================================


        __syncthreads();
        // move T_dev_ptr 1 column
        T_dev_ptr = PtrColOffset_ColM(T_dev_ptr,1,T_dev.outerStrideBytes);
        // move mu_ptr to nex contact
        __syncthreads();
        mu_dev_ptr = &mu_dev_ptr[1];
        __syncthreads();

     }
     __syncthreads();
     // Write back the results, dont need to syncronize because 
     // do it anyway to be safe for testing first!

     if(thid < m){
        y_dev.data[ii] = xx[thid]; THIS IS UPDATED IN KERNEL B
        t_dev.data[ii] = tt[thid]; THIS IS UPDATED IN KERNEL B
     }


     //__threadfence(); /// THIS STUPID THREADFENCE MAKES IT WORKING!

我将最后的解决方案与CPU进行比较,然后我把它放在任何地方,我只能为了安全而开始! (这段代码高斯塞德尔的东西) 但是如果没有THREAD_FENCE在END或BEGINNIG它没有意义的话它根本不起作用......

很抱歉这么多代码,但可能你可以猜到问题出在哪里,因为我有点在我的最后,解释为什么会发生这种情况? 我们多次检查算法,没有内存错误(从Nsight报告)或 其他东西,每件事都运行正常......内核A只用ONE Block启动!

2 个答案:

答案 0 :(得分:4)

如果将内核的连续实例启动到同一个流中,则每个内核启动与其之前和之后的内核实例相比是同步的。编程模型保证了它。 CUDA只允许在启动到同一上下文的不同流中的内核上同时执行内核,即使这样,只有在调度程序确定有足够的资源可用时才会发生重叠内核执行。

__threadfence__syncthreads都不会产生你似乎想到的效果 - __threadfence仅适用于所有活动线程的范围,__syncthreads是一个内部线程阻挡屏障操作。如果您真的想要内核到内核同步,则需要使用其中一个主机端同步调用,例如cudaThreadSynchronize(前CUDA 4.0)或cudaDeviceSynchronize(cuda 4.0及更高版本),或者每个流相当于你使用流。

答案 1 :(得分:2)

虽然我对您的体验感到有些惊讶,但我相信您的解释可能是正确的。

除了原子函数之外,写入全局内存不能保证其他线程(来自相同或不同的块)立即可见。通过放置__threadfence(),您可以暂停当前线程,直到写入实际上可见。当您使用具有缓存的全局内存(Fermi系列)时,这可能很重要。

有一点需要注意:内核调用是异步的。当您的第一个内核调用由GPU处理时,主机可能会发出另一个调用。下一个内核将与您当前的内核并行运行,但会在当前内核完成后立即启动,从根本上隐藏由CPU与GPU通信引起的延迟。

使用cudaThreadSynchronise暂停主机线程,直到完成所有CUDA任务。它可能会对您有所帮助,但它也会阻止您隐藏CPU-&gt; GPU通信延迟。请注意,使用同步内存访问(例如cudaMemcpy,没有“异步”后缀)的行为也类似于cudaThreadSynchronise