TSX之有限事務內存,TSX之鎖消除技術 和 lock free 編程之比較(有效總結)

TSX(事務同步擴展)

TSX是新一代Haswell架構上,通過硬件支持的事務性內存(Transactional Memory)解決方案。HLE和RTM都在事務的基礎上實現的硬件技術手段。一句話概括Intel的事務性同步擴展(Transactional Synchronization Extension, TSX)的動機:粗粒度鎖保證的事務性操作,在高併發下性能下降,作爲細粒度鎖方案的一種替代,TSX通過硬件輔助保證正確性,編程更友好。

TSX overview

適用的場景:

一張有大量數據的表(原講座中用銀行賬戶記錄做比),一種典型的事務性操作,是從其中一個條目a中減去一個數值x,加到條目b中:

[ a = a – x : b = b + x]                                (1)

        如果用一個粗粒度的鎖保護整張表的操作,在併發時會碰到如下的問題,比如同時另一個線程對不同的條目c和d操作,原本不衝突的操作,因爲粗粒度鎖的存在,不得不串行執行。可以想見,高併發下粗粒度鎖的方案性能嚴重下降。

        傳統的優化手段是使用細粒度的鎖,比如給表中的每一個條目單獨加鎖,那麼上面存在的“假”的數據衝突就可以避免。但是細粒度鎖極大增加的設計的複雜度,容易出現難以解決的bug。作爲一個例子,考慮在(1)的操作同時,另一個線程進行如下操作:

[ b = b – x : a = a + x]                                (2)

        由於a/b由兩個獨立的鎖保護,完成(1)或(2)的操作,需要獲得兩把鎖,如果(1)(2)不能完整獲得兩把鎖,而是(1)獲得lock(a),(2)獲得lock(b),即出現死鎖。所以細粒度鎖的加鎖解鎖方案需要仔細設計,避免死鎖和其他很多問題。

        使用TSX的替代方案:邏輯上TSX是一把粗粒度的鎖,將包含事務性的操作的critical section包起來;由硬件自動檢測操作中的數據衝突,保證事務性操作的正確性,發掘操作間的並行性,實現上類似每個條目都有細粒度的鎖,這被稱作lock elision。

 不適用的場景:

       從上面的例子可以看出,TSX主要解決的是粗粒度鎖下的“假”數據衝突問題,如果原本不需要細粒度的鎖,或者產生衝突的條目少,“真”衝突概率高,那麼使用TSX的收益不大。TSX不是銀彈。

Q: 我怎麼知道什麼時候該使用TSX?

A: 如果現在的程序沒有性能問題,你可以去休息,喝杯咖啡;如果有我上面場景中的性能問題,你可以試試TSX,用起來也很方便。

性能

典型應用場景下,相對粗粒度鎖的方案,TSX的方案在高併發下的性能有明顯提升,可以達到接近線性的可擴展性;相對細粒度鎖的方案,TSX在高併發下的性能也有小的優勢(TSX的開銷可能比細粒度鎖的開銷小)。圖不方便貼了。

相比比較火的無鎖編程,TSX也有明顯的優勢。無鎖編程不是真的沒有鎖,而是很強的依賴於原子操作,它的劣勢是限制了數據結構(只能用隊列等),難於設計和優化,高併發下也有問題。TSX下數據結構的選擇更自由(因爲使用的是和粗粒度鎖一樣的臨界區的模型),同樣的需求用無鎖編程難以驗證正確性。

底層TM跟蹤事務的read-set 和write-set ,以一個64字節的高速緩存行爲粒度。這裏的read-set 和write- set 分別表明事務在執行過程中已讀出或寫入的所有高速緩存行。 如果事務的read-set中的一個高速緩存行被另一個線程寫入,或者事務的 write-set 中的一個高速緩存行被另一個線程讀取或寫入,則事務就遇到衝突(conflict)。 對於那些熟悉TM術語的人而言,這就是所謂的強隔離(strong isolation),因爲一個非事務性(non-transactional)內存訪問可能會導致事務中止(abort)。 衝突通常導致事務中止,且在一個高速緩存行內還可能發生假衝突(falseconflicts)。

TSX可以允許事務相互嵌套,在概念上是通過將嵌套展平成單個事務來處理的。 然而,嵌套的數量有一個具體實現特定的限制,超過此限制將導致中止。 在一個嵌套事務內的任何中止,都將中止所有的嵌套事務。

事務只能使用可高速緩存的回寫內存操作(write-back cacheable memory operations),但可以在所有的權限級別下使用。不是所有的x86指令都可安全地用於事務內。 有幾個x86指令將導致任何事務(對於HLE或 RTM)中止,特別是CPUID和PAUSE。

此外,在特定的實現中,還有些可能導致中止的指令。 這些指令包括x87和MMX,混合訪問XMM和YMM寄存器,EFLAGS寄存器的非狀態部分的更新,更新段(segment),調試(debug )或控制(control )寄存器,ring 轉換,高速緩存和TLB控制指令,任何非寫回(non-writeback)內存類型的訪問,處理器狀態保存,中斷,I / O,虛擬化(VMX),可信執行(SMX)以及一些雜項類型。 注意,這意味着上下文切換(context switch),通常使用狀態保存(state saving)指令,幾乎總是會中止事務。 一般來說,TSX 實現的目的是要相對於指令向上兼容。 例如,如果一個實現在事務中新增對 VZEROUPPER的支持,則該功能將不會在未來的版本中刪除。

也有運行時的行爲可能會導致事務中止。大多數的錯誤和陷阱都會導致中斷,而同步例外和異步事件可能會導致中止。自修改代碼和訪問非寫回內存類型也可能導致中止。 

事務的大小也有具體實現特定的限制,且各種微架構的緩衝(buffers)可能是限制因素。 一般情況下,英特爾對有關事務的執行沒有提供保證。雖然對於程序員而言這是令人沮喪的,因爲它需要一個非事務性的(non-transactional)回退路徑,這樣就避免了未來的向後兼容性問題。即便當程序員寫了一個不太可能成功的事務,它也避免了使系統死鎖。

軟件

操作系統不需要改變。

主流編譯器支持:ICC v13.0以上,GCC v4.8以上,Microsoft VS2012以上。

庫:GLIBC的pthread rtm-2.17分支支持。

(我:找到網上有一個C版本的TSX使用例子,http://software.intel.com/en-us/blogs/2012/11/06/exploring-intel-transactional-synchronization-extensions-with-intel-software)

Q: pthread中怎麼使用TSX?

A: 只需要動態鏈接這個版本的pthread庫就可以(我:看來pthread使用了TSX重構了一些代碼,而不包括TSX的高級封裝)。

代碼介紹

TSX的模型類似傳統的臨界區。提供兩種編程接口:HLE(HardwareLock Elision)和RTM(Restricted Transactional Memory)。以如下的僞代碼爲例:

acquire_lock(mutex);

// critical section   

release_lock(mutex)

帶鎖的代碼

傳統的基於鎖的方案大概是這樣的:

       mov eax, 1

Try:   lockxchg mutex, eax

      cmp  eax, 0

      jzSuccess

Spin:  pause

      cmp  mutex, 1

      jz  Spin

      jmp  Try

; critical section …

Release:   mov  mutex, 0

HLE(Hardware Lock Elide/硬件鎖消除) code

使用一對compilerhints:xacquire /xrelease。

mov eax, 1

Try:   xacquire xchg mutex, eax

      cmp  eax, 0

       jz Success

Spin:  pause

      cmp  mutex, 1

      jz  Spin

      jmp  Try

; critical section …

Release:   xrelease  mutex, 0

提示:

(1)   兩個關鍵詞是hints,在不支持TSX的硬件上直接被忽略。所以兼容性好。

(2)   事務性操作失敗(abort)的結果是重新執行傳統的有鎖代碼(legacy code)。

 

RTM(Restricted Transaction Memory/有限事務內存) Code

RTM使用兩條新的指令標識critical section:xbegin /xend。

RTM的模型更加靈活:

Retry: xbeginAbort

      cmp  mutex, 0

      jzSuccess

       xabort$0xff

Abort:

       …check%EAX

       …doretry policy

       …

      cmp  mutex, 0

      jnz  Release_lock

       xend

提示:

(1)   事務性操作失敗(abort)的後續操作入口由xbegin指定。

(2)   xabort指令通過eax返回一個錯誤碼,用於後續分原因處理。

 

無鎖編程

定義:

相關技術:

當你試圖滿足無鎖編程的無阻塞條件時,會出現一系列技術:原子操作、內存屏障、避免ABA問題。

依賴於原子操作

謂原子操作是指,通過一種看起來不可分割的方式來操作內存:線程無法看到原子操作的中間過程。

原子操作一:對簡單類型的對齊的讀和寫

在現代的處理器上,有很多操作本身就是的原子的。例如,對簡單類型的對齊的讀和寫通常就是原子的。這個依賴平臺,有待測試。

原子操作二:Read-Modify-Write(RMW)操作

1)        RMW操作的例子包括:Win32上的_InterlockedIncrementiOS上的OSAtomicAdd32以及C++11中的std::atomic<int>::fetch_add。需要注意的是,C++11的原子標準不保證其在每個平臺上的實現都是無鎖的,因此最好要清楚你的平臺和工具鏈的能力。你可以調用std::atomic<>::is_lock_free來確認一下。不同的CPU系列支持RMW的方式也是不同的。

2)         最常討論的RMW操作是compare-and-swap(CAS)。在Win32上,CAS是通過如_InterlockedCompareExchange等一系列指令來提供的。通常,程序員會在一個事務中使用Compare-And-Swap循環。這個模式通常包括:複製一個共享的變量至本地變量,做一些特定的工作(改動),再試圖使用CAS發佈這些改動。在intelCMPXCHG指令基礎上。

//函數完成的功能是:將old和ptr指向的內容比較,如果相等,則將new寫入到ptr中,返回old,如果不相等,則返回ptr指向的內容。

Linux cmpxchg cmpxchg(void *ptr, unsigned long old, unsigned longnew);

//如果第三個參數與第一個參數指向的值相同,那麼用第二個參數取代第一個參數指向的值。函數返回值爲原始值。

Windows LONG InterlockedCompareExchange(LPLONGDestination, LONG Exchange, LONG Comperand ); PVOIDInterlockedCompareExchangePointer(PVOID *Destination, PVOIDExchange, PVOID Comperand );

 voidenQ(request, queue)

{

    do{

       local_head = queue->head;

       request->next = queue->head;

        val =cmpxchg(&queue->head, 

       local_head, request);

    }while(val !=local_head)

}

void LockFreeQueue::push(Node* newHead)

{

   for (;;)

    {

       // Copy a shared variable (m_Head) to a local.

       Node* oldHead = m_Head;

 

        // Do some speculative work, not yet visibleto other threads.

       newHead->next = oldHead;

 

       // Next, attempt to publish our changes to the shared variable.

       // If the shared variable hasn't changed, the CAS succeeds and wereturn.

        // Otherwise, repeat.

       if (_InterlockedCompareExchange(&m_Head, newHead, oldHead) ==oldHead)

           return;

    }

}

ABA 問題

在進行CAS操作的時候,因爲在更改V之前,CAS主要詢問“V的值是否仍然爲A”,所以在第一次讀取V之後以及對V執行CAS操作之前,如果將值從A改爲B,然後再改回A,會使基於CAS的算法混亂。在這種情況下,CAS操作會成功。這類問題稱爲ABA問題。

對於CAS產生的這個ABA問題,通常的解決方案是採用CAS的一個變種DCAS。
DCAS,是對於每一個V增加一個引用的表示修改次數的標記符。對於每個V,如果引用修改了一次,這個計數器就加1。然後再這個變量需要update的時候,就同時檢查變量的值和計數器的值。

要解決ABA問題,就不要重用A。通常通過將標記或版本編號與要進行CAS操作的每個值相關聯,並原子地更新值和標記,來處理這類問題。

在像Java或者.net這樣的具有垃圾收集機制的環境中,這個問題很簡單,只要不循環使用V即可。也就是說,一旦V第一次被使用,就不會再重複使用,如有需要則分配新的V。垃圾收集器可以檢查V,保證其不被循環使用,直到當前的訪問操作全部結束。(這個做法用垃圾收集器來控制對V的訪問,相當於有個線程本地變量了)

三種技術總結

TSX-HLE 特點:

(1)      依然維護鎖的概念,但是不真正操作鎖,許多線程可以同時獲得鎖,並對共享數據做無衝突的內存訪問:就是鎖地地被加到事務內存的read-set, 但是不寫入數據到鎖地址,線程接着進入事務區域執行,期間HLE事務內部讀該鎖地址都將返回新數據(譯註:該新數據雖然沒有寫入鎖地址,但是被本地硬件保持,在讀這個鎖地址時返回),用以造成鎖已經被獲得的假象,實際上並沒有做獲得鎖的操作。但另一個線程的任何讀操作都將返回舊數據(真正的鎖地址沒有被寫入數據,鎖是unlocked)。

(2)      如果數據衝突,那麼丟棄原來事務數據,帶鎖從新執行一遍:事務執行過程中,如果和其他線程發生數據衝突,處理器體系結構寄存器(architectural registers)的狀態將恢復到XACQUIRE之前,並丟棄任何在HLE區域內寫入的內存。 線程將再次執行該區域,就像沒有HLE,而使用標準的悲觀鎖行爲(pessimistic lockingbehavior)。 

(3)      假鎖的數量有具體實現特定的限制。

(4)      儘管嵌套的HLE是存在的,但是遞歸鎖是不被支持的。

(5)      在HLE區域內對鎖地址進行寫操作,將導致HLE中止。

(6)      衝突檢測是64bit寬度的, read-set 和write-set ,以一個64字節的高速緩存行爲粒度。譬如int x,y; &x==0x00; &y==0x32;那麼事務期間操作x,y都被操作將被視爲衝突。

TSX-RTM特點

(1)      不在維護鎖的概念,直接在事務中執行代碼

(2)      如果數據衝突,那麼丟棄原來事務數據到abort代碼段,abort的具體實現由程序員決定,可以帶鎖執行也可以retry一遍RTM. 另外,如果兩個事務衝突,都可能被中止。 一個更明智的做法是中止兩個事務其中之一。

(3)      支持事務嵌套,估計支持遞歸事務。

(4)      衝突檢測是64bit寬度的。

無鎖編程

與spinlock 相比:無鎖編程其實是個死循環,但是和spinlock的循環不一樣。 Spinlock是圍繞着鎖,先去拿鎖,如果拿不到,那麼spin back to re-get 鎖。無鎖編程是不維護鎖的概念,先把數據備份到用戶內存中修改數據,如果沒有衝突(copy出來的值和原來的值相等)那麼就利用原子操作提交數據。與spinlock相比,RMW基礎上的CAS在沒有衝突的情況下會不涉及到鎖,避免大部分的lock/unlock操作,另外CAS是指令級別的原子性,最後一點CAS把數據備份出來並且檢查衝突這其實就是一個事務。

與intel TSX相比: 自己維護事務內存(變量本地備份,RWM進行衝突檢查);沒有鎖的概念,CAS原語進行交換賦值;如果有衝突,重新進行一個新的事務,與TSX中的RTM相似,只是事務簡單很多;自己維護的事務比較簡單,所以存在ABA問題,如要解決必須進行版本管理。

參考文章:

//無鎖編程之翻譯介紹

http://m.blog.csdn.net/blog/sahusoft/9210029

//事務同步擴展

本節參考: http://blog.csdn.net/linxxx3/article/details/8788153

//事務同步擴展 2

http://www.cnblogs.com/coryxie/archive/2013/02/24/2951244.html

 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章