Как синхронизировать глобальную память между несколькими запусками ядра? - PullRequest
0 голосов
/ 01 июля 2011

Я хочу несколько раз запустить следующее ядро ​​в 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 завершаются после одного ядра ...: -)

Спасибо за ваши ответы!

НЕКОТОРЫЙ КОД:

 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!

Я сравниваю решение в конце с процессором, и ЗДЕСЬ я помещаю его везде, где только можно, чтобы быть в безопасности, для начала!(этот код делает gauss seidel), но он не работает вообще без THREAD_FENCE в END или в BEGINNIG, где он не имеет смысла ...

Извините за такой большой код, но, вероятно, вы можетеугадайте, где возникла проблема, потому что я немного не в себе и объясню, почему это происходит?Мы несколько раз проверили алгоритм, нет ошибки памяти (сообщается из Nsight) или чего-то другого, все работает нормально ... Ядро A запускается только с ОДНЫМ блоком!

Ответы [ 2 ]

4 голосов
/ 01 июля 2011

Если вы запускаете последовательные экземпляры ядра в одном и том же потоке, каждый запуск ядра происходит синхронно по сравнению с экземпляром ядра до и после него. Модель программирования это гарантирует. CUDA разрешает одновременное выполнение ядра только для ядер, запущенных в разных потоках одного и того же контекста, и даже тогда перекрывающееся выполнение ядра происходит только в том случае, если планировщик определяет, что для этого доступно достаточно ресурсов.

Ни __threadfence, ни __syncthreads не будут иметь эффекта, о котором вы, похоже, думаете - __threadfence работает только в области действия всех активных потоков, а __syncthreads - это операция внутриблокового барьера. Если вы действительно хотите, чтобы ядро ​​синхронизировалось с ядром, вам нужно использовать один из вызовов синхронизации на стороне хоста, например cudaThreadSynchronize (до CUDA 4.0) или cudaDeviceSynchronize (cuda 4.0 и более поздние версии), или эквивалент для каждого потока, если вы используя потоки.

2 голосов
/ 01 июля 2011

Хотя я немного удивлен тем, что вы испытываете, я считаю, что ваше объяснение может быть правильным.

Запись в глобальную память, за исключением атомарных функций, не гарантируется, что она будет немедленно видна другим потокам (из того же самого или из разных блоков). Помещая __threadfence(), вы останавливаете текущий поток, пока записи фактически не будут видны. Это может быть особенно важно, когда вы используете глобальную память с кешем (серия Fermi).

Стоит отметить: вызовы ядра являются асинхронными. Пока ваш первый вызов ядра обрабатывается графическим процессором, хост может выполнить другой вызов. Следующее ядро ​​будет не работать параллельно с вашим текущим, но запустится, как только закончится текущее, существенно сократив задержку, вызванную связью CPU-> GPU.

Использование cudaThreadSynchronise останавливает поток хоста до тех пор, пока не будут выполнены все задачи CUDA. Это может помочь вам, но также не позволит вам скрыть задержку связи CPU-> GPU. Обратите внимание, что использование синхронного доступа к памяти (например, cudaMemcpy, без суффикса "Async") по сути ведет себя как cudaThreadSynchronise.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...