为什么计算单源最短路径需要第二个CUDA内核?

为什么计算单源最短路径需要第二个CUDA内核?,cuda,Cuda,我参考了一篇研究论文,在CUDA上实现了单源最短路径算法。 有两个内核,如下所示 __global__ void SSSP_kernel_1(Node* d_node, int* d_edges, int *d_weights, bool* d_mask, int* d_cost, int *d_costU, unsigned long long no_of_nodes) { int tid = blockIdx.x * blockDim.x + threadIdx.x; if

我参考了一篇研究论文,在CUDA上实现了单源最短路径算法。 有两个内核,如下所示

__global__ void SSSP_kernel_1(Node* d_node, int* d_edges, int *d_weights, bool* d_mask, int* d_cost, int *d_costU, unsigned long long no_of_nodes) { 
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    if(tid < no_of_nodes && d_mask[tid]) { 
        d_mask[tid] = false;
        for(int i = d_node[tid].start; i < (d_node[tid].start + d_node[tid].num); i++) { 
            int id = d_edges[i];
            if(d_costU[id] > (d_cost[tid] + d_weights[i]))
            d_costU[id] = d_cost[tid] + d_weights[i]; 
        } 
    } 
} 

__global__ void SSSP_kernel_2(Node* d_node, int* d_edges, int *d_weights, bool* d_mask, int* d_cost, int *d_costU, unsigned long long no_of_nodes, bool *d_stop) { 
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    if(tid < no_of_nodes) {
        if(d_cost[tid] > d_costU[tid]) {
            d_cost[tid] = d_costU[tid];
            d_mask[tid] = true;
            *d_stop = false;
        }
        d_costU[tid] = d_cost[tid];
    }
}
使用中间数组计算成本,并启动第二个内核来更新成本值。作者说,在修改时更新成本本身 可能导致读写不一致。但我不明白为什么。即使写后有读操作,我也会更新第8行,内核1只更新最小的值,这是无论如何需要的。我错过了什么? 谢谢你抽出时间

编辑: 我提到的那篇论文
第7页

由于同步需要,作者将操作分为两个内核,因为通过这种方式,所有线程都可以看到成本更新

在第一个核中,访问由d_mask选择的所有顶点,并计算其对应邻居的新代价。如果当前成本d_成本[tid]加上边缘权重d_权重[i]小于旧成本d_成本[id],则必须更新成本。更新必须在旧的成本变量d_costU[id]中执行,否则,如果在当前变量d_cost[tid]中操作,则该更新将仅对线程的子集可见


第二个内核检查是否为每个顶点找到了较小的成本,如果是,则将其标记为需要访问,并更新当前成本变量d_cost[tid]。然后将旧成本变量d_costU[tid]设置为当前的d_costU[tid]

仔细阅读了你链接到的那篇文章后,以下是我的想法。对不起,有点长,请耐心点

通过将两个内核合并为一个内核,我们得到以下实现,我希望您会同意:

__global__ void SSSP_kernel(Node* d_node, int* d_edges, int *d_weights,
                            bool* d_mask, int* d_cost, int *d_costU,
                            unsigned long long no_of_nodes, bool *d_stop) {
    int tid = blockIdx.x * blockDim.x + threadIdx.x;

    if (tid < no_of_nodes && d_mask[tid]) {
        d_mask[tid] = false;

        for (int i = d_node[tid].start; i < (d_node[tid].start + d_node[tid].num); i++) {
            int id = d_edges[i];

            if (d_cost[id] > (d_cost[tid] + d_weights[i])) {
                d_cost[id] = (d_cost[tid] + d_weights[i]);
                d_mask[id] = true;
                *d_stop = false;
            }
        }
    }
}
如果在if条件和车身之间写入d_成本[tid],则可能发生危险。结果是d_成本[id]的新值不一致。有问题吗?我认为事实并非如此:d_成本[tid]的价值只能降低,因此没有违反该条件。通过引入辅助变量,可以解决精确的危险,如下所示:

int cost = d_cost[tid] + d_weights[i];
if (d_cost[id] > cost) {
    d_cost[id] = cost;
    ...
}
危险仍然可能发生,但现在根据d_成本[id]的值:有可能用不好的成本覆盖更好的成本,这将违反条件。有问题吗?同样,我认为情况并非如此:它只会推迟找到正确的解决办法,而不会阻止它。事实上,这种危险也可能发生在你发布的2内核版本中,你自己在评论中也说过。这可以通过使用原子指令来解决,作者在论文中提到了这一点

我认为这两种危险,在所有其他条件相同的情况下,不会阻止算法收敛到正确的解,但它们会使成本矩阵暂时处于不一致的状态

二,。写入d_掩码

每次更新节点id的成本时,都会将其标记为必须使用以下行刷新邻居的成本:

d_mask[id] = true;
这是一封写给d_mask的信。另一个写入是在检查是否必须刷新节点邻居时:

if (tid < no_of_nodes && d_mask[tid]) {
    d_mask[tid] = false;
那么这些操作如何排序呢

一个选项是,在写入false之后可能会发生写入true:d_mask[tid]的最终值为true。这不是一个问题,因为它不会影响算法的正确性:节点将在下一次迭代中再次被访问,可能是冗余的,并且邻居将被刷新

但是考虑当写到true发生在写到false之前会发生什么:DyMask[TID]的最终值将是false。这意味着在下一次迭代中将不会再次访问该节点,因此在当前迭代中计算的所有成本将被冻结,可能永远冻结。因此,必须使用在将掩码设置为true的线程上计算的最新可用成本来正确计算它们。但由于SMs无法同步其内存操作,因此这些操作可能还不可用。这可能会阻止算法收敛到正确的解。正如作者所提到的,这种危险将随着原子学的使用而消失


总之,出现“先读后写”的危险是因为在一次又一次迭代中只刷新了一部分节点:如果在每一步都刷新了所有节点,则算法将始终收敛到正确的解决方案。

即使在更新中间数组时,也不会发生同样的情况吗?嗨,非常感谢您的详细解释。如果我理解正确,您指出更新成本值没有问题——尽管存在危险,但将在以下迭代中解决。危险存在于d_面罩上。d_mask设置为false的点是当一个节点刷新其邻居时,因此即使它覆盖了一个真值,它也不会使用线程更新的最新成本值进行刷新,该线程将其设置为真,因此
你是对的,我运行了两个内核和一个内核的版本,一些顶点被冻结,成本更高。但是,当我看到前面评论中指出的代码时,我看不出原因。如果您是第一个评论,则几乎是正确的!:当d_mask设置为false时,节点将使用最新的可用值刷新其邻居。如果这些值是在同一个SM上计算的,那么它们肯定对该线程可用。但是如果它们是在另一个SM上计算的,这是不能保证的,因为内存子系统是如何工作的,缓存等。最新的值将在下一次内核启动时对所有线程可用,但是到那时d_mask被设置为false,所以刷新不会发生。这就是造成危害的原因:节点被冻结,成本不正确。哦,我一直认为对全局内存的任何更新都会在各种短信中保持一致。关于这一点,有什么文件我可以进一步阅读吗?此外,如果我必须使用单个内核编写此代码-我需要原子地更新掩码和成本值?您可能希望读取特定设备的内存层次结构及其缓存机制,它在不同的体系结构中有所不同。如果您想使用单个内核编写此代码,只要我的分析是正确的,那么只需要对成本进行原子更新。您好,即使它在当前迭代中仅对一部分变量可用,在下一次迭代中是否对所有线程都可用?
if (tid < no_of_nodes && d_mask[tid]) {
    d_mask[tid] = false;