解讀Disruptor系列-Disruptor論文精選

本文是筆者在研究Disruptor過程中翻譯的Disruptor1.0論文精選,中間穿插了一些感想和說明,均以“譯注”的形式說明鼓拧。
論文原地址:https://lmax-exchange.github.io/disruptor/files/Disruptor-1.0.pdf

Disruptor: 用于并發(fā)線程間數據交換的有界隊列的高性能替代

  • Martin Thompson
  • Dave Farley
  • Michael Barker
  • Patricia Gee
  • Andrew Stewart
    May-2011

1 摘要

LMAX是為了創(chuàng)造一個極高性能的金融交易所而成立。作為實現這一目標的工作的一部分,我們已經評估了這種系統(tǒng)的幾種設計方法娱节,但是隨著開始測試质涛,我們遇到了使用傳統(tǒng)方法的一些基本限制带饱。
許多應用程序依賴隊列在不同的處理階段之間交換數據。我們的性能測試表明酪耕,以這種方式使用隊列時的延遲成本與IO操作到磁盤(基于RAID或SSD的磁盤系統(tǒng))的成本大致處于相同的數量級 - 非常非常慢盟步。如果端對端操作中有多個隊列,將為總延遲增加數百微秒。顯然有優(yōu)化的空間。
進一步的調查和對計算機科學的關注使我們認識到驶睦,傳統(tǒng)方法固有問題的結合(例如隊列和處理節(jié)點) 溉痢,導致了多線程實現中的爭用竹挡,這表明可能有更好的方法好啰。
考慮現代CPU如何工作,我們喜歡稱之為“機械同情”,使用良好的設計實踐闹司,強調關注焦點,我們提出了一種我們稱之為Disruptor的數據結構和使用模式沐飘。
測試表明游桩,使用Disruptor進行三級流水線的平均延遲比基于隊列的等效方法低3個數量級牲迫。此外,Disruptor在相同配置下處理大約8倍的吞吐量借卧。
這些性能改進代表著圍繞并行編程的思考的一個改變盹憎。這種新模式是需要高吞吐量和低延遲的任何異步事件處理架構的理想基礎。
在LMAX铐刘,我們已經建立了一個訂單匹配引擎陪每,實時風險管理和一個高可用的內存中事務處理系統(tǒng),所有這一切都取得了巨大的成功镰吵。這些系統(tǒng)中的每一個都制定了新的性能標準檩禾,據我們所知,這些標準是無與倫比的疤祭。
然而盼产,這并不是一個只有在金融業(yè)才有意義的專業(yè)解決方案。 Disruptor是一種通用的機制勺馆,它能夠以最大化性能的方式解決并發(fā)編程中的復雜問題戏售,而且實現起來也很簡單。盡管有些概念看起來很不尋常谓传,但我們的經驗是蜈项,建立在這種模式上的系統(tǒng)要比類似的機制要簡單得多。
Disruptor顯著減少了寫時爭用续挟,并發(fā)開銷較低,并且比可當前其他方法更加緩存友好侥衬,所有這些都會導致更高的吞吐量诗祸,更低的延遲且具有較少的抖動。 在中等時鐘速率的處理器上轴总,我們已經看到每秒超過2500萬條消息直颅,延遲低于50納秒。 與我們看到的任何其他實現相比怀樟,這種性能是一個明顯的改進功偿。 這非常接近現代處理器在內核之間交換數據的理論限制。
(譯注:一句話總結往堡,現有的隊列結構限制了并發(fā)編程的效率械荷,Disruptor用于代替隊列實現高效地線程間的數據交換。)

2 概述

Disruptor是我們努力在LMAX建立世界最高性能金融交易所的結果虑灰。 早期的設計集中在從SEDA和Actors派生的架構吨瞎,使用管道(pipelines)提升吞吐量。 在對各種實現進行了分析之后穆咐,很明顯颤诀,在管道之間的各個階段之間的排隊是控制成本的主要因素字旭。我們發(fā)現隊列也引入了延遲和高水平的抖動。 我們花了大量精力開發(fā)具有更好性能的新隊列實現崖叫。 然而遗淳,由于對生產者,消費者和他們的數據存儲的設計問題的混淆心傀,作為基本數據結構的隊列變得非常有限屈暗。 Disruptor是我們的工作成果,用于構建一個能將這些問題劃分清楚的并發(fā)結構剧包。

3并發(fā)的復雜性

在本文檔和計算機科學的上下文中恐锦,并發(fā)不僅意味著兩個或多個任務并行發(fā)生,而且他們也在爭奪資源的訪問疆液。競爭資源可能是數據庫一铅,文件,套接字甚至內存中的位置堕油。
代碼的并發(fā)執(zhí)行大約是兩件事领突,互斥和變化的可見性〔饽互斥是關于管理某些資源的競爭更新昂验。變化的可見性是關于控制何時使這些更改對其他線程可見。如果可以消除對競爭更新的需求眶明,可以避免互斥的需要艰毒。如果您的算法能夠保證任何給定的資源只被一個線程修改,那么就不需要相互排斥搜囱。讀和寫操作要求所有的更改都對其他線程可見丑瞧。然而,只有爭用的寫操作需要相互排斥更改蜀肘。
任何并發(fā)環(huán)境中绊汹,最昂貴的操作是爭用的寫訪問。要將多個線程寫入相同的資源需要復雜且昂貴的協(xié)調扮宠。通常情況下西乖,這是通過采用某種鎖定策略實現的。

3.1 鎖的成本

通過操作系統(tǒng)內核的上下文切換實現坛增,會暫停線程去等待鎖直到釋放获雕。執(zhí)行這樣的上下文切換,會丟失之前保存的數據和指令轿偎。

循環(huán)5億次64位計數操作使用時間對比:

image.png

3.2 CAS的成本

CAS(Compare And Swap)操作是特殊的機器碼指令典鸡,允許以原子操作對內存中的字(word)進行有條件地設置。如x86上的"lock cmpxchg"坏晦。
對于“遞增計數器實驗”萝玷,每個線程可以循環(huán)讀取計數器嫁乘,然后嘗試將其原子地設置為其新的遞增值。舊的和新的值作為該指令的參數提供球碉。如果在執(zhí)行操作時蜓斧,計數器的值與提供的預期值相匹配,計數器將用新值更新睁冬。另一方面挎春,如果實際值與預期值不等,CAS操作將失敗豆拨。然后由線程嘗試執(zhí)行更改以重試直奋,重新讀取從該值增加的計數器,依此類推施禾,直到更改成功脚线。
可以不斷進行重讀、重試弥搞,直到操作成功邮绿。與鎖相比,避免了上下文切換帶來的巨大開銷攀例。但是并非沒有開銷船逮。處理器必須鎖定指令流水線(instruction pipeline)來保證原子性,并且使用一個內存屏障(memory barrier)使變化對于其他線程可見粤铭。
最優(yōu)算法是:一個線程執(zhí)行所有寫操作挖胃,其他線程讀取結果。在多處理器環(huán)境下讀取結果需要內存屏障來保證變化對運行在其他處理器上的線程可見梆惯。

3.3 內存屏障

出于性能原因冠骄,現代處理器使用亂序的指令執(zhí)行亂序的數據載入及存儲(內存和執(zhí)行單元間)。處理器需要保證的只是加袋,程序邏輯結果不受亂序影響
處理器使用內存屏障來指示內存更新排序很重要的代碼段抱既。它們是在線程之間實現硬件排序和改變可見性的手段职烧。 編譯器可以添加軟件屏障,以確保編譯代碼的排序防泵,這些軟件內存障礙是作為處理器硬件屏障的附加而由處理器使用的蚀之。
讀內存屏障通過在“內存變化無效隊列”做標記,來排序CPU裝載指令捷泞。寫內存屏障通過在用于刷寫的存儲緩沖中做標記足删,來排序存儲指令。一個完整的內存屏障只在執(zhí)行它的CPU上排序裝載和存儲(指令)锁右。
JAVA中失受,通過volatile關鍵字實現讀寫屏障讶泰。由Java5內存模型明確定義。

3.4 緩存行

現代處理器的緩存用法對于成功的高性能操作至關重要拂到。目前來說痪署,處理器在處理緩存中的數據和指令非常高效,相反的兄旬,當緩存缺失(cache miss)時則非常低效狼犯。
硬件并非以字節(jié)或字長為單位處理內存。為了高效率领铐,緩存被組織成大小為32到256字節(jié)的緩存行(cache-line)悯森,最常用的是64字節(jié)。這是高速緩存一致性協(xié)議的粒度級別绪撵。
上面的意思是:如果有兩個變量在相同的緩存行中瓢姻,且這兩個變量是被不同線程寫入,這是就會存在寫入爭用問題莲兢,就好像這兩個變量是一個變量一樣(兩個線程雖然操作的是各自的變量汹来,但會導致同一個緩存行失效)。這就是偽共享(false sharding)改艇。從高性能上考慮收班,重要的是要確保爭用最小化,獨立但并發(fā)寫入的變量不會共享相同的高速緩存行谒兄。當以可預測的方式訪問主存時摔桦,CPU可以通過預測將要訪問的位置進行預讀取來隱藏訪問主存的成本。但是如果是遍歷鏈表或樹這樣的結構時承疲,CPU就不能預測下一步的訪問位置了邻耕。這將最終導致訪問效率下降2個數量級。

3.5 Queue的問題

Queue通常使用鏈表或數組結構存儲元素燕鸽。使用無界隊列通常都需要保證生產者生產速度小于消費者消費速度兄世,否則將會由于耗盡內存導致災難結果。為了避免這種結果啊研,隊列通常是有界的御滩。保持隊列有界要么使用數組,要么就是能夠主動跟蹤大小党远。
隊列實現傾向于對head削解,tail和size變量進行寫入爭用。 在使用中沟娱,由于消費者和生產者之間的差異氛驮,隊列通常總是接近滿或接近空济似。 他們很少在平衡的中間地帶進行生產和消費的比例均勻匹配矫废。 總是充滿或總是空的傾向導致高水平的爭用和/或昂貴的緩存一致性盏缤。 問題是,即使使用諸如鎖或CAS變量的不同并發(fā)對象來分離頭尾機制磷脯,它們通常也占用相同的高速緩存行蛾找。
使用一個粗粒度的鎖管理整個queue的put和take非常容易實現,但是也很容易成為吞吐量瓶頸赵誓。而使用更細粒度的控制就需要更復雜的并發(fā)設計打毛。如果將并發(fā)問題從queue的語義中剔除,queue的實現無非就是單生產者-單消費者的模式俩功。
在Java中幻枉,使用隊列還有一個問題-隊列是垃圾的重要來源。首先诡蜓,必須分配對象并將其放置在隊列中熬甫。 其次,如果支持鏈表蔓罚,則必須分配表示列表節(jié)點的對象椿肩。 當不再引用時,需要重新聲明分配給支持隊列實現的所有這些對象豺谈。
(譯注:簡單總結郑象,使用隊列時,由于消費者生產者之間的差異茬末,導致并發(fā)損耗厂榛;存在偽共享問題;隊列元素是垃圾重要來源丽惭。)

3.6 流水線和圖表

對于許多類問題击奶,將幾個處理階段組織成(wire together)管道是有意義的。 這樣的管道通常具有并行路徑责掏,被組織成圖形狀的拓撲(topologies)柜砾。 每個階段之間的鏈接通常由隊列實現,每個階段都有自己的線程换衬。
這種方法并不廉價 - 在每個階段局义,都必須承擔入隊和出隊的消耗。 當路徑必須分叉(fork)時冗疮,目標的數量乘以這個成本,并且在這樣一個分支之后必須重新加入時會產生不可避免的爭用成本(譯注:如一個事件既需要被記錄日志檩帐,也需要被業(yè)務邏輯消費)术幔。
如果依賴圖的表達不會導致階段之間放置隊列的消耗,將是最理想的湃密。(譯注:將不同階段通過隊列組成拓撲圖時诅挑,避免隊列本身的消耗)

4 LMAX Disruptor的設計思想

設計Disruptor就是為了解決以上問題四敞。之所以叫做"Disruptor",是因為在Java7中拔妥,用于支持Fork-join的"Phaser"在處理依賴圖表時有相似的地方忿危。(譯注:Phaser相位槍、Disruptor裂解炮都是星際迷航中的武器没龙,Phaser是聯邦武器铺厨,Disruptor是克林貢的等價物)

4.1 內存分配

環(huán)形緩沖(Ring Buffer)的所有內存都是在啟動時預分配的。環(huán)形緩沖既可以存儲條目(entry)指針的數組硬纤,也可以存儲代表條目的結構數組解滓。Java語言限制了條目以對象指針的形式與環(huán)形緩沖相關聯。每個條目都并非數據本身筝家,而是一個容器洼裤。條目的預分配消除了支持垃圾回收編程語言的問題,所有的條目在Disruptor實例生存周期中都將重用并一直存在溪王。這些條目的內存同時被分配腮鞍,將有極大可能在主存中連續(xù)分配,這樣就支持了緩存位置預測(cache striding莹菱,緩存跨越)移国。這是由John Rose提出的建議,將“值類型”引入到java語言芒珠,允許類似C語言中的元組數組那樣桥狡,來保證內存的連續(xù)分配而避免指針間接尋址。
在受管理的運行時環(huán)境(如Java)中開發(fā)低延遲系統(tǒng)時皱卓,垃圾收集可能會有問題裹芝。 分配的內存越多,垃圾收集器的負擔就越大娜汁。 垃圾收集者在對象生命周期非常短暫或有效永生(譯注:而非內存泄露引起的永生)的情況下工作在最佳狀態(tài)嫂易。 在環(huán)形緩沖區(qū)中預先分配條目意味著對于垃圾收集器而言,它是永生的掐禁,因此代表著很小的負擔怜械。
在重負載隊列系統(tǒng)中進行備份,會引起處理速率的下降傅事,導致已分配對象生存的時候更久缕允,將會被垃圾收集器提升到老年代。這意味著兩點:一不同代之間的對象復制會造成延遲抖動蹭越;二垃圾收集器回收老年代對象代價更大障本,而且當內存碎片空間需要壓縮時,會增加“Stop the world”停頓的可能性。在大內存堆中可能會導致每GB幾秒的停頓驾霜。

4.2 梳理問題

我們在所有隊列實現中總結了以下問題:
a. 被交換元素如何存儲
b. 如何協(xié)調生產者聲明(claiming 取得)下一個用于交換(exchange)的序號
c. 在新元素可用時如何協(xié)調要通知的消費者

在使用垃圾回收的語言設計金融交易系統(tǒng)時案训,內存分配過多可能會造成問題。 所以粪糙,正如我們描述的使用支持鏈表的隊列不是一個好辦法强霎。 如果可以預先分配用于處理階段之間的數據交換的整個存儲空間,則垃圾收集將被最小化蓉冈。 此外城舞,如果這種分配可以在統(tǒng)一的塊中執(zhí)行,則將以對現代處理器采用的緩存策略非常友好的方式來完成該數據的遍歷洒擦。 滿足此要求的數據結構是預先填充所有插槽的數組椿争。 在創(chuàng)建環(huán)形緩沖區(qū)時,Disruptor利用抽象工廠模式預先分配條目熟嫩。 當聲明條目時秦踪,生產者可以將其數據復制到預先分配的結構中。
在大多數處理器上掸茅,對序列號進行余數計算的成本非常高椅邓,這決定了環(huán)中的插槽。 通過使環(huán)形緩沖器大小為2的平方昧狮,可以大大降低該成本景馁。可以使用大小(size)減去1的位掩碼來有效地執(zhí)行求余(remainder)操作。(如size為8逗鸣,序號為14合住,則序號1110 & 111 = 110即6)(譯注:注意,此處原文應該有誤撒璧,實際上這里應為求模操作透葛,這部分的源碼會在后續(xù)講
就像先前描述的,有界隊列的頭部和維護遭受爭用卿樱。環(huán)形緩沖器的數據結構免除了這種爭用和并發(fā)原語僚害,因為這些問題被轉移到了環(huán)形緩沖器關聯的生產者和消費者的屏障(barrier)中。屏障邏輯如下:
大多數情況下Disruptor只使用一個生產者繁调。通常生產者是文件讀取器或網絡監(jiān)聽器萨蚕。當只有一個生產者時,當然沒有序號(sequence)和條目分配的爭用蹄胰。
在有多個生產者的不常見情況下岳遥,多個生產者將會競爭聲明(claim)環(huán)形緩沖器中下一個條目。這種(多生產者對同一個條目的)爭用可以使用一個簡單的CAS操作得到管理裕寨。
當生產者將關聯數據復制進聲明的條目中后寒随,會通過提交這個條目的序號讓此條目對消費者可見。這可以不使用CAS而使用一個簡單的忙碌旋轉(busy spin)直到其他生產者在各自的提交中到達該序號。生產者可以提前一個游標表示下一個可用條目的消費妻往。生產者可以在寫入環(huán)形緩沖區(qū)之前,通過跟蹤消費者的序號作為簡單的讀取操作來避免環(huán)繞環(huán)试和。
消費者在讀取條目前先等待環(huán)形緩沖器中的序列可用讯泣。有多種等待策略可供選擇。如果CPU資源很寶貴阅悍,消費者可以做鎖的條件等待生產者發(fā)送通知好渠。這種做法會造成爭用,只有在CPU資源比延遲或吞吐更重要時才會用节视。消費者也可以循環(huán)檢查代表當前可用序列的游標拳锚。這樣可以使用CPU資源交換更小的延遲,可以選擇用或者不用線程退讓(thread yield)寻行。如果我們不使用鎖和條件變量霍掺,我們已經打破了生產者和消費者之間的競爭依賴關系,這樣做非常好拌蜘。無鎖的多生產者 - 多消費者隊列確實存在杆烁,但是它們需要在頭部,尾部简卧,大小計數器上進行多個CAS操作兔魂。 Disruptor不能忍受這樣的CAS爭用。

4.3 序列(Sequencing)

序列是Disruptor中管理并發(fā)的核心概念举娩。每個生產者和消費者在同環(huán)形緩沖器交互時析校,都采用嚴格的序列概念。當生產者要求環(huán)形中一個條目時铜涉,會要求序列中的下一個槽位智玻。下一個可用槽位的序列可以是單生產者時的簡單計數器,或者是多生產者時的使用CAS操作的原子計數器骄噪。當序列值被聲明時尚困,環(huán)形緩沖器中的這個條目就可以被那個特定的生產者寫入了。當生產者完成更新條目時链蕊,它可以通過更新單獨的計數器來提交更改事甜,該計數器表示環(huán)形緩沖區(qū)上的光標,以為消費者提供可用的最新條目滔韵。環(huán)形緩沖器游標可以被生產者使用內存屏障以忙循環(huán)(busy spin)讀取和寫入逻谦,而無須使用以下的CAS操作:

long expectedSequence = claimedSequence – 1;
while (cursor != expectedSequence) {
  // busy spin
}
cursor = claimedSequence;

消費者通過使用內存屏障來讀取游標等待給定的序列可用。 一旦光標被更新陪蜻,內存屏障就可以確保在緩沖區(qū)中條目的更改對于等待光標前進的消費者是可見的邦马。
每個消費者都包含一個自己的處理環(huán)形緩沖器條目的序列。這些消費者序列允許生產者跟蹤消費者,防止繞環(huán)(譯注:prevent the ring from wrapping)滋将。消費者序列還允許消費者以有序的方式在同一條目上協(xié)調工作邻悬。
在只有一個生產者的情況下,無論消費者圖表的復雜程度如何随闽,都不需要鎖或CAS操作父丰。 可以通過上述討論的序列上的內存屏障來實現整個并發(fā)協(xié)調。

4.4 批量效應(Batching Effect)

當消費者等待環(huán)形緩沖器下一個游標序列時掘宪,可能會出現一個有趣的不太可能在隊列上出現的現象蛾扇。如果消費者發(fā)現環(huán)形緩沖區(qū)游標自上次檢查以來已經前進了多步,則可以(譯注:從落后位置)一直處理到該序號位置魏滚,而不會涉及并發(fā)機制镀首。 這導致滯后的消費者在生產者加速向前時迅速趕上生產者的步伐,從而平衡系統(tǒng)鼠次。這種批處理增加了吞吐量更哄,同時減少和平滑了延遲⌒刖欤基于我們的觀察結果竖瘾,這種效應不論負載如何將總是保持常量時間的延遲,直到存儲子系統(tǒng)飽和花颗,根據利特爾法則(Little's Law)捕传,輪廓是線性的。這與我們在時隊列觀察到的延遲的“J”曲線效應非常不同扩劝。
(譯注:代碼實現為BatchEventProcessor)

4.5 依賴圖(Dependency Graphs)

一個隊列代表了生產者和消費者之間僅一步流水線依賴庸论。如果消費者組成了一個依賴的鏈條或圖表結構,那么在圖結構每個階段間都需要隊列棒呛。在依賴階段的圖中聂示, 這樣增加了許多次的隊列固定消耗。在設計LMAX金融交易系統(tǒng)時簇秒,我們的分析表明鱼喉,使用基于隊列方法的消耗決定了處理交易的總消耗。
因為在Disruptor模式中趋观,生產者和消費者問題是分開的扛禽,只在核心的一個環(huán)形結構中表示出一個消費者復雜依賴圖就成為了可能。這導致執(zhí)行的固定成本大大降低皱坛,從而提升了吞吐量同時減少了延遲编曼。
單一一個環(huán)形緩沖器可以用來存儲可以表達整個工作流復雜結構的條目。必須注意這種結構的設計剩辟,在被獨立消費者寫入狀態(tài)時不能導致偽共享問題掐场。
(譯注:多任務流水線之間可能有依賴往扔,如ABC為三個任務,A->B->C是基本的流程熊户,在多線程任務中萍膛,只有完成A的線程才能執(zhí)行B,再執(zhí)行C嚷堡。)

4.6 Disruptor類設計

Disruptor框架的核心關系類圖描述如下卦羡。圖中省略了一些可以簡化編程模型的工具類。生產者通過ProcuderBarrier按順序要求條目麦到,將變化寫入要求的條目,再通過ProcuderBarrier把條目提交回去欠肾,讓它們可供消費瓶颠。每個消費者需要做的是提供一個BatchHandler實現,用于接收新條目可用時的回調刺桃。這種編程模型很類似Actor模型的事件驅動模型粹淋。將通常合并在一起的隊列實現問題分離開,將允許更大的設計自由度瑟慈。一個環(huán)形緩沖器是Disruptor模式核心存在桃移,提供了無爭用的數據存儲交換。并發(fā)問題也被分成生產者和消費者同環(huán)形緩沖器的交互葛碧。ProducerBarrier管理任意在環(huán)形緩沖器中聲明槽位的并發(fā)問題借杰,同時跟蹤獨立的消費者來阻止環(huán)繞環(huán)(prevent the ring from wrapping)。當新條目可用時进泼,ConsumerBarrier通知消費者蔗衡,消費者可以被構造成一個處理流水線的多階段依賴圖。

image.png

(譯注:Disruptor自發(fā)布以來已有若干變化乳绕。
Entry -> Event
Consumer -> EventProcessor
ProcuderBarrier -> AbstractSequencer
Consumer -> EventProcessor
ConsumerBarrier -> SequenceBarrier)

4.7 代碼樣例

單生產者和單消費者模式绞惦。(譯注:由于代碼設計發(fā)生了較大變化,以下代碼只用作理解)

// Callback handler which can be implemented by consumers
final BatchHandler batchHandler = new BatchHandler()
{
     public void onAvailable(final ValueEntry entry) throws Exception
     {
      // process a new entry as it becomes available.
     }
     public void onEndOfBatch() throws Exception
     {
     // useful for flushing results to an IO device if necessary.
     }
     public void onCompletion()
     {
     // do any necessary clean up before shutdown
     }
};

RingBuffer ringBuffer = new RingBuffer(ValueEntry.ENTRY_FACTORY, SIZE, ClaimStrategy.Option.SINGLE_THREADED, WaitStrategy.Option.YIELDING);
ConsumerBarrier consumerBarrier = ringBuffer.createConsumerBarrier();
BatchConsumer batchConsumer = new BatchConsumer(consumerBarrier, batchHandler);
ProducerBarrier producerBarrier = ringBuffer.createProducerBarrier(batchConsumer);

// Each consumer can run on a separate thread
EXECUTOR.submit(batchConsumer);
 
// Producers claim entries in sequence ValueEntry
entry = producerBarrier.nextEntry();

// copy data into the entry container
 
// make the entry available to consumers
producerBarrier.commit(entry);

5 吞吐量性能測試

作為參考洋措,我們選擇Doug Lea的出色的java.util.concurrent.ArrayBlockingQueue济蝉,基于我們的測試它在任何有界隊列中具有最高性能。 測試以阻塞編程風格進行菠发,以匹配Disruptor的編程風格王滤。 以下詳細的測試案例可在Disruptor開源項目中找到。 注意:運行測試需要能夠并行執(zhí)行至少4個線程的系統(tǒng)雷酪。
(譯注:方便起見淑仆,使用P1代表第一個生產者,P2代表第二個生產者哥力,C1代表第一個消費者蔗怠,C2代表第二個消費者墩弯,以此類推)
單播:1P-1C
P1-> C1
三步流水線:1P-3C
P1 -> C1 -> C2 -> C3
多生產者: 3P-1C
P1,P2,P3 -> C1
多播(多消費者):1P-3C
P1 -> C1, C2, C3
鉆石型:1P-3C
P1 -> C1, C2 -> C3

image.png

對于上述配置寞射,與Disruptor的屏障配置相比渔工,每個數據流弧應用了ArrayBlockingQueue。 下表顯示了使用Java 1.6.0_25 64位Sun JVM桥温,Windows 7引矩,不帶超線程的Intel Core i7 860 @ 2.8 GHz和Intel Core i7-2720QM(Ubuntu 11.04)的操作的性能結果,并充分利用3次處理5億條消息的運行結果侵浸。 不同的JVM執(zhí)行結果可能會有很大差異旺韭,下面的數字并不是我們觀察到的最高結果。

image.png

單位:操作/秒

6 延遲性能測試

為了測量延遲掏觉,我們采用三級流水線区端,并以小于飽和度的速度生成事件。這是通過在注入事件之前等待1微秒澳腹,然后再注入下一個并重復5000萬次來實現织盼。要按照這個精度要求,需要使用CPU的時間戳計數器(TSC)酱塔。我們選擇具有不變的TSC的CPU沥邻,因為由于省電和睡眠狀態(tài),較舊的處理器遭受頻率變化的影響羊娃。英特爾Nehalem和更高級的處理器使用一個不變的TSC唐全,可以在Ubuntu 11.04上運行的最新的Oracle JVM訪問。該測試沒有使用CPU綁定迁沫。
為了比較芦瘾,我們再次使用ArrayBlockingQueue。我們可以使用ConcurrentLinkedQueue集畅,這可能會給出更好的結果近弟,但是我們希望使用有界隊列實現來確保生產者不要超過消費者創(chuàng)造背壓。以下結果是2.2Ghz Core i7-2720QM在Ubuntu 11.04上運行Java 1.6.0_25 64位挺智。
Disruptor的平均延遲時間為52納秒祷愉,而ArrayBlockingQueue32,757納秒。分析顯示使用鎖和信令通過條件變量是ArrayBlockingQueue的延遲的主要原因赦颇。

image.png
image.png

7 結論

Disruptor是提高吞吐量二鳄,減少并發(fā)執(zhí)行上下文之間的延遲并確保可預測延遲的重大進步媒怯,這在許多應用程序中是一個重要的考慮因素订讼。我們的測試表明,它比在線程間交換數據的同類方法性能要好扇苞。我們認為這是這種數據交換的最高性能機制欺殿。通過關注跨線程數據交換中涉及的關注點的清晰分離寄纵,通過消除寫入爭用、最小化讀取爭用和確保代碼與現代處理器使用的緩存工作良好脖苏,我們已經創(chuàng)建了一種高效的機制來在任何應用程序之間交換數據程拭。
允許消費者在沒有任何爭用的情況下處理條目達到給定閾值的批處理效應,在高性能系統(tǒng)中引入了一個新特性棍潘。對于大多數系統(tǒng)恃鞋,當負載和爭用增加時,延遲指數增加亦歉,即典型的“J”曲線恤浪。隨著Disruptor上的負載增加,延遲保持幾乎平坦肴楷,直到內存子系統(tǒng)發(fā)生飽和资锰。
我們相信Disruptor建立了高性能計算的新基準,并且非常適合繼續(xù)利用當前處理器和計算機設計的趨勢阶祭。

引用:
1 Staged Event Driven Architecture – http://www.eecs.harvard.edu/~mdw/proj/seda/
2 Actor model – http://dspace.mit.edu/handle/1721.1/6952
3 Java Memory Model - http://www.ibm.com/developerworks/library/j-jtp02244/index.html
4 Phasers -
http://gee.cs.oswego.edu/dl/jsr166/dist/jsr166ydocs/jsr166y/Phaser.html
5 Value Types - http://blogs.oracle.com/jrose/entry/tuples_in_the_vm
6 Little’s Law - http://en.wikipedia.org/wiki/Little%27s_law
7 ArrayBlockingQueue - http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ArrayBlockingQueue.html
8 ConcurrentLinkedQueue -
http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市直秆,隨后出現的幾起案子濒募,更是在濱河造成了極大的恐慌,老刑警劉巖圾结,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瑰剃,死亡現場離奇詭異,居然都是意外死亡筝野,警方通過查閱死者的電腦和手機晌姚,發(fā)現死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歇竟,“玉大人挥唠,你說我怎么就攤上這事』酪椋” “怎么了宝磨?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盅安。 經常有香客問我唤锉,道長,這世上最難降的妖魔是什么别瞭? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任窿祥,我火速辦了婚禮,結果婚禮上蝙寨,老公的妹妹穿的比我還像新娘晒衩。我一直安慰自己嗤瞎,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布浸遗。 她就那樣靜靜地躺著猫胁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪跛锌。 梳的紋絲不亂的頭發(fā)上弃秆,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音髓帽,去河邊找鬼菠赚。 笑死,一個胖子當著我的面吹牛郑藏,可吹牛的內容都是我干的衡查。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼必盖,長吁一口氣:“原來是場噩夢啊……” “哼拌牲!你這毒婦竟也來了?” 一聲冷哼從身側響起歌粥,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤塌忽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后失驶,有當地人在樹林里發(fā)現了一具尸體土居,經...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡胞枕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年狸捕,在試婚紗的時候發(fā)現自己被綠了耙考。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姨蟋。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡范嘱,死狀恐怖吉拳,靈堂內的尸體忽然破棺而出暮芭,到底是詐尸還是另有隱情想鹰,我是刑警寧澤胎围,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布账磺,位于F島的核電站,受9級特大地震影響痊远,放射性物質發(fā)生泄漏垮抗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一碧聪、第九天 我趴在偏房一處隱蔽的房頂上張望冒版。 院中可真熱鬧,春花似錦逞姿、人聲如沸辞嗡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽续室。三九已至栋烤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挺狰,已是汗流浹背明郭。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丰泊,地道東北人薯定。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像瞳购,于是被迫代替她去往敵國和親话侄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理学赛,服務發(fā)現年堆,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,110評論 25 707
  • 以下盏浇,都是在生活中我所見到的有趣的事嘀韧。如果你恰好了解,卻覺得平常缠捌,也請不要見怪。 1译蒂、當你一直看著某個人曼月,那個...
    陸清禾閱讀 696評論 2 0
  • 知識經常被認為是儲存在昏暗圖書館里堆滿塵埃的書架上的死物。遺憾的是柔昼,圖書館里沉寂的氣氛會使人想起教堂的葬禮或...
    鄧潔兒閱讀 156評論 0 1
  • 不能再說沒時間碼字捕透,沒時間學習聪姿。畢竟,追的劇實在是太多了乙嘀,時間自然而然也就沒了末购,沒了,沒了…… 01 美劇《Min...
    差點叫冬梅的lily閱讀 183評論 0 0