這是我的理解(請參閱,例如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