这是我的理解(请参阅,例如How can I enforce CUDA global memory coherence without declaring pointer as volatile?,CUDA block synchronization differences between GTS 250 and Fermi devices和this post in the nvidia Developer Zone)__threadfence()
保证在线程继续之前全局写入对其他线程可见。但是,在__threadfence()
已返回之后,另一个线程仍然可以从L1高速缓存读取陈旧值,即使也是如此。__threadfence()和L1高速缓存一致性
即:
线程A的一些数据写入到全局存储器,然后调用__threadfence()
。然后,在之后__threadfence()
已经返回,并且写入可见到全部其他线程,线程B被要求从这个内存位置读取。它发现它具有L1中的数据,因此会加载该数据。不幸的是,对于开发者来说,线程B的L1中的数据是陈旧的(即和线程A更新这个数据一样)。
首先:这是正确的吗?
假如是的话,它似乎我__threadfence()
只有有用的,如果任何一个可以是某些该数据不会在L1(有点不太可能?),或者如果例如读取总是绕过L1(例如易失性或原子性)。它是否正确?
我问,因为我有一个比较简单的用例 - 传播的数据了一个二叉树 - 用原子级组标志和__threadfence()
:第一个线程到达某个节点退出,而第二写入数据到它基于其两个孩子(例如他们的最小数据)。这适用于大多数节点,但通常至少失败一次。声明数据volatile
可获得始终如一的正确结果,但会导致99%以上的未从L1中获取陈旧值的情况下的性能下降。我想确定这是该算法的唯一解决方案。下面给出了一个简化的例子。请注意,节点数组的排列宽度优先,叶子从索引start
开始,并已填充数据。
__global__ void propagate_data(volatile Node *nodes,
const unsigned int n_nodes,
const unsigned int start,
unsigned int* flags)
{
int tid, index, left, right;
float data;
bool first_arrival;
tid = start + threadIdx.x + blockIdx.x*blockDim.x;
while (tid < n_nodes)
{
// We start at a node with a full data section; modify its flag
// accordingly.
flags[tid] = 2;
// Immediately move up the tree.
index = nodes[tid].parent;
first_arrival = (atomicAdd(&flags[index], 1) == 0);
// If we are the second thread to reach this node then process it.
while (!first_arrival)
{
left = nodes[index].left;
right = nodes[index].right;
// If Node* nodes is not declared volatile, this occasionally
// reads a stale value from L1.
data = min(nodes[left].data, nodes[right].data);
nodes[index].data = data;
if (index == 0) {
// Root node processed, so all nodes processed.
return;
}
// Ensure above global write is visible to all device threads
// before setting flag for the parent.
__threadfence();
index = nodes[index].parent;
first_arrival = (atomicAdd(&flags[index], 1) == 0);
}
tid += blockDim.x*gridDim.x;
}
return;
}
在你的推理中,'volatile'关键字的作用基本上是禁止使用缓存。现在,你的问题是L1缓存不一致。但L2是连贯的。那么禁用L2缓存而不是使用'volatile'呢? – JackOLantern
禁用L2缓存?你是怎样做的? –
@RobertCrovella对不起,罗伯特,这是一个误印。我的意思是禁用L1(不是L2),这是由'-Xptxas -dlcm = cg'完成的。我的理解,也似乎从你的回答中推断出来的,就是使用'volatile',并结合'__threadfence()',绕过L1。所以,我想知道禁用L1缓存与使用'volatile'结合使用'__threadfence()'会有什么效果。缺点是'-Xptxas -dlcm = cg'会在整个执行过程中禁用L1缓存,而'volatile'则是“选择性的”。 – JackOLantern