我想在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启动!
答案 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
。