2011-07-25 72 views
8

我寫了一些無鎖的代碼,在大多數情況下可以與當地的 一起使用。自旋鎖總是需要記憶障礙嗎?旋轉在記憶障礙上是否昂貴?

本地在讀取內存時是否必然意味着我必須始終在旋轉 之前插入內存屏障?

(爲了驗證這一點,我設法產生一個讀/寫器組合 這導致讀取器從未看到 寫入的值,在某些非常特殊的條件 - 專用CPU,過程連接到CPU, 優化器接通一路向上,在 環沒有進行任何的工作 - 這樣的箭頭做點這個方向發展,但我不 完全確信通過存儲紡 屏障的成本)

是什麼。通過內存屏障旋轉的代價,如果 沒有什麼要在緩存的存儲區中刷新e緩衝區? 即所有的進程正在做(在C)是

while (1) { 
    __sync_synchronize(); 
    v = value; 
    if (v != 0) { 
     ... something ... 
    } 
} 

我是正確的假設,它是免費的,它不會與任何業務拖累 內存總線?

另一種方式把這個要問:沒有一個內存屏障做 什麼比更多:刷新存儲緩衝區,應用 廢票給它,並防止 重新排序的編譯器讀取在其位置/寫?


拆卸,__sync_synchronize()似乎翻譯成:

lock orl 

從英特爾手冊(同樣含糊不清的新手):

Volume 3A: System Programming Guide, Part 1 -- 8.1.2 

Bus Locking 

Intel 64 and IA-32 processors provide a LOCK# signal that 
is asserted automatically during certain critical memory 
operations to lock the system bus or equivalent link. 
While this output signal is asserted, requests from other 
processors or bus agents for control of the bus are 
blocked. 

[...] 

For the P6 and more recent processor families, if the 
memory area being accessed is cached internally in the 
processor, the LOCK# signal is generally not asserted; 
instead, locking is only applied to the processor’s caches 
(see Section 8.1.4, 「Effects of a LOCK Operation on 
Internal Processor Caches」). 

我的翻譯:「當你說LOCK,這將會很昂貴,但我們只在需要的地方使用 。「


@BlankXavier:

我做了測試,如果作家沒有明確推出從存儲緩衝區寫入,它是CPU上運行的唯一的過程中,讀者可以永遠看作者的作用(我可以用測試程序重現它,但正如我上面提到的,它只發生在特定的測試中,具有特定的編譯選項和專用的核心分配 - 我的算法工作正常,只有當我好奇時關於這是如何工作的,並寫下了明確的測試,我意識到它可能會出現問題)。

我認爲默認情況下,簡單的寫入是WB寫入(回寫),這意味着它們不會立即刷新,但讀取將取最近的值(我認爲他們稱之爲「存儲轉發」)。所以我爲作者使用CAS指令。我在英特爾手冊中發現了所有這些不同類型的寫入實現(UC,WC,WT,WB,WP),英特爾第3A卷第11-10章,仍然在瞭解它們。

我的不確定性在讀者身上:我從McKenney的論文中瞭解到,還有一個無效隊列,即從總線進入緩存的無效隊列。我不確定這部分是如何工作的。特別是,您似乎暗示循環讀取正常(即,非LOCK'ed,沒有屏障,並且僅使用volatile來確保優化器在編譯之後保留讀取)將每次都檢查到「無效隊列」 (如果存在這樣的事情)。如果一個簡單的讀取不夠好(也就是說可以讀取一箇舊的緩存行,該行在排隊失效之前仍然顯示有效(這對我來說聽起來有點不一致,但是失效隊列如何工作呢?)),那麼原子讀取會是必要的,我的問題是:在這種情況下,這會對公共汽車產生什麼影響嗎? (我想可能不是)

我仍然在閱讀我通過英特爾手冊的方式,雖然我看到商店轉發的一個很好的討論,但我還沒有找到關於失效隊列的很好的討論。我決定將我的C代碼轉換爲ASM並進行實驗,我認爲這是真正理解它如何工作的最好方法。

+3

「在大多數情況下可以正常讀取。」 - 如果它總是無法正常工作,那麼它就不好...... –

+0

關於全面優化的小循環測試,還有其他一些問題,例如: [Cyrix昏迷錯誤](http://en.wikipedia.org/wiki/Cyrix_coma_bug#Analysis)(儘管它不適用於這種情況),這可能會影響「假」測試。 –

+0

@Mitch:我的,當然,這就是爲什麼我問:-) – blais

回答

2

我可能無法正確理解了問題,但...

如果你打轉,一個問題是編譯器優化旋轉遠。揮發性解決了這個問題。

內存屏障,如果有的話,將由作者發給自旋鎖,而不是讀者。作者實際上並沒有使用一個 - 這樣做可以確保寫入立即被推出,但它很快就會熄滅。

屏障阻止執行該代碼的線程在其位置上重新排序,這是其它成本。

+0

在上面的修改中回答,所以SO不會讓我添加長評論。 – blais

4

「xchg reg,[mem]」指令將通過內核的LOCK引腳發出鎖定意圖。該信號通過其他內核並緩存到總線主控總線(PCI變種等),這將完成他們正在做的事情,並且最終LOCKA(確認)引腳將向CPU發信號通知CPU可以完成xchg。然後關閉LOCK信號。此序列可能需要很長時間(數百個CPU週期或更長時間)才能完成。之後,其他內核的相應緩存行將會失效,並且您將擁有一個已知狀態,即已經在內核之間同步的狀態。

xchg指令是實現原子鎖所需要的。如果鎖定本身成功,則可以訪問已定義鎖定以控制對其的訪問的資源。這樣的資源可能是一個內存區域,一個文件,一個設備,一個功能或你有什麼。儘管如此,程序員總是可以編寫代碼,當它被鎖定時使用這個資源,而不是當它沒有時。通常,成功鎖定後的代碼序列應該儘可能短,以使其他代碼儘可能少地阻礙獲取對資源的訪問。

請記住,如果鎖定不成功,則需要通過發出新的xchg再次嘗試。

「無鎖」是一個吸引人的概念,但它需要消除共享資源。如果您的應用程序有兩個或兩個以上的內核,同時從共同的內存地址讀取和寫入,則「無鎖定」不是一個選項。

0

請記住,障礙通常用於對內存訪問集進行排序,因此您的代碼很可能在其他地方也可能需要屏障。例如,它不會是罕見的屏障要求是這樣的,而不是:

while (1) { 

    v = pShared->value; 
    __acquire_barrier() ; 

    if (v != 0) { 
     foo(pShared->something) ; 
    } 
} 

這個屏障將防止如果塊加載和存儲(即:pShared->something)從執行前value加載完成。一個典型的例子是,你有一些使用v != 0一家商店標誌「製片人」,其他一些內存(pShared->something)在其他一些預期的狀態,如:

pShared->something = 1 ; // was 0 
__release_barrier() ; 
pShared->value = 1 ; // was 0 

在這個典型的生產消費情況,你幾乎總是需要成對的障礙,一個用於標記輔助存儲器可見的存儲器(以便在存儲器之前沒有看到存儲器的效果)以及消費者的一個障礙(使得某些負載在值加載完成之前未啓動)。

這些障礙也是平臺特定的。例如,在powerpc上(使用xlC編譯器),分別爲消費者和生產者使用__isync()__lwsync()。需要什麼屏障還可能取決於您用於存儲和加載value的機制。如果您使用了導致英特爾LOCK(也許是隱含的)的原子內在屬性,那麼這將引入一個隱含的障礙,所以您可能不需要任何東西。此外,你可能還需要明智地使用volatile(或者最好使用一個原子實現,在下面這樣做),以便讓編譯器做你想做的事情。