我参考了一篇研究论文,在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];
}
}
使用中间数组计算成本,启动第二个内核以更新成本值。作者说&#34;在修改本身时更新成本 写入不一致会导致读取。&#34;但我不明白为什么。即使写入后读取,我也会更新(第8行,内核1)只有最小值,这是无论如何需要。我在这里错过了什么? 谢谢你的时间
编辑: 我提到的那篇论文 http://cvit.iiit.ac.in/papers/Pawan07accelerating.pdf(第7页)
答案 0 :(得分:2)
由于同步需要,作者在两个内核中打破操作,因为这样他们可以使所有线程都可以看到成本更新。
在第一个内核中,访问d_mask
选择的所有顶点,并评估其相应邻居的新成本。如果当前费用d_cost[tid]
加上边缘权重d_weights[i]
小于旧费用d_costU[id]
,则必须更新费用。必须在旧的成本变量d_costU[id]
中执行更新,否则,如果它在当前变量d_cost[tid]
中运行,则该更新仅对线程的子集可见。
第二个内核检查是否为每个顶点找到了较小的成本,如果是,则将其标记为需要访问并更新当前成本变量d_cost[tid]
。然后将旧的成本变量d_costU[tid]
设置为当前的d_cost[tid]
。
答案 1 :(得分:2)
仔细阅读你所链接的论文后,这是我的想法。对不起,这有点长,请耐心等待。
通过将两个内核合并为一个内核,我们得到以下实现,我希望您同意这一点:
__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;
}
}
}
}
让我们假设这是作者在提及写后读写危险时所指的实现方式。如果这个假设是错误的,那么这个答案可能毫无意义。但是我们假设这是真的并继续:哪里可能发生写后读取危险?
正如Robert Crovella在他(现已删除)的回答中所说,内核中只有两个有趣的位置写:d_cost
和d_mask
。我们来看两个。
<强> 1。写入 d_cost
考虑以下代码节:
if (d_cost[id] > (d_cost[tid] + d_weights[i])) {
d_cost[id] = (d_cost[tid] + d_weights[i]);
...
}
如果d_cost[tid]
在if
条件和<{em>}之间写入,则可能会发生危险。结果是d_cost[id]
的新值不一致。这有问题吗?我认为不是:d_cost[tid]
的价值只能减少,因此不会违反条件。通过引入一个辅助变量可以解决这个精确的危险:
int cost = d_cost[tid] + d_weights[i];
if (d_cost[id] > cost) {
d_cost[id] = cost;
...
}
危险仍然可能发生,但现在d_cost[id]
的价值:可能会以更好的成本覆盖“更好的成本”(这会违反条件)。这有问题吗?同样,我认为不是:它只会延迟找到正确的解决方案,而不是阻止它。事实上,这个危险也可能也发生在你发布的2内核版本中,你在评论中自己说了。这可以通过使用原子指令来解决(作者在论文中提到它)。
我认为这两种危险,其他条件相同,并不妨碍算法收敛到正确的解决方案,但它们会使成本矩阵处于不一致状态(暂时)。
<强> 2。写入 d_mask
每次更新节点id
的费用时,都会将其标记为必须刷新邻居费用,其中包含以下行:
d_mask[id] = true;
这是对d_mask
的一次写作。另一个写入是在检查是否必须刷新节点邻居时:
if (tid < no_of_nodes && d_mask[tid]) {
d_mask[tid] = false;
那么如何对这些操作进行排序呢?
一个选项是,在true
写入后,对false
的写入可能会发生:d_mask[tid]
的最终值为true
。这不是问题,因为它不会影响算法的正确性:在下一次迭代中将再次访问节点(可能是冗余的),并且将刷新邻居。
但请考虑一下true
写入发送之前发生的事件写入false
:d_mask[tid]
的最终值为false
。这意味着节点将在下一次迭代中再次访问 ,因此在当前迭代中计算的所有成本将被冻结,可能永远冻结。因此,必须使用在将掩码设置为true
的线程上计算的最新可用成本正确计算它们。但由于SM无法同步其内存操作,因此这些可能尚不可用。这可能会阻止算法收敛到正确的解决方案。正如作者所提到的,这种危险会随着原子的使用而消失。
总之,写入后读取危险的出现是因为只有一部分节点从迭代到刷新被刷新:如果所有节点都在每一步都刷新,那么算法总会收敛到正确的解决方案。