55理解disr下不需要換擋和踩剎車的有多快_第1頁(yè)
55理解disr下不需要換擋和踩剎車的有多快_第2頁(yè)
55理解disr下不需要換擋和踩剎車的有多快_第3頁(yè)
已閱讀5頁(yè),還剩5頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、55-理解Disruptor(下):不需要換擋和踩剎的CPU,有多快?上講,我們學(xué)習(xí)了個(gè)精妙的想法,Disruptor通過(guò)緩存填充,來(lái)利好CPU的速緩存。不知道你做完課后思考題之后,有沒(méi)有體會(huì)到速緩存在實(shí)踐中帶來(lái)的速度提升呢?不過(guò),利CPU速緩存,只是Disruptor“快”的個(gè)因素,那今天我們就來(lái)看看Disruptor快的另個(gè)因素,也就是“鎖”,盡可能發(fā)揮CPU本的速處理性能。緩慢的鎖Disruptor作為個(gè)性能的產(chǎn)者 消費(fèi)者隊(duì)列系統(tǒng),個(gè)核的設(shè)計(jì)就是通過(guò)RingBuer實(shí)現(xiàn)個(gè)鎖隊(duì)列。上講我們講過(guò),Java的基礎(chǔ)庫(kù),就有像LinkedBlockingQueue這樣的隊(duì)列庫(kù)。但是,這個(gè)隊(duì)列庫(kù)起D

2、isruptor的RingBuer要慢上很多。慢的第個(gè)我們說(shuō)過(guò),因?yàn)殒湵淼臄?shù)據(jù)在內(nèi)存的布局對(duì)于速緩存并不友好,RingBuer所使的數(shù)組則不然。LinkedBlockingQueue慢,有另外個(gè)重要的因素,那就是它對(duì)于鎖的依賴。在產(chǎn)者 消費(fèi)者模式,我們可能有多個(gè)消費(fèi)者,同樣也可能有多個(gè)產(chǎn)者。多個(gè)產(chǎn)者都要往隊(duì)列的尾指針添加新的任務(wù),就會(huì)產(chǎn)多個(gè)線程的競(jìng)爭(zhēng)。于是,在做這個(gè)事情的時(shí)候,產(chǎn)者就需要拿到對(duì)于隊(duì)列尾部的鎖。同樣地,在多個(gè)消費(fèi)者去消費(fèi)隊(duì)列頭的時(shí)候,也就產(chǎn)競(jìng)爭(zhēng)。同樣消費(fèi)者也要拿到鎖。那只有個(gè)產(chǎn)者,或者個(gè)消費(fèi)者,我們是不是就沒(méi)有這個(gè)鎖競(jìng)爭(zhēng)的問(wèn)題了呢?很遺憾,還是的。般來(lái)說(shuō),在產(chǎn)者 消費(fèi)者模式下,消

3、費(fèi)者要產(chǎn)者快。不然的話,隊(duì)列會(huì)產(chǎn)積壓,隊(duì)列的任務(wù)會(huì)越堆越多。,你會(huì)發(fā)現(xiàn)越來(lái)越多的任務(wù)沒(méi)有能夠及時(shí)完成;另,我們的內(nèi)存也會(huì)放不下。雖然產(chǎn)者 消費(fèi)者模型下,我們都有個(gè)隊(duì)列來(lái)作為緩沖區(qū),但是部分情況下,這個(gè)緩沖區(qū)是空的。也就是說(shuō),即 使只有個(gè)產(chǎn)者和個(gè)消費(fèi)者者,這個(gè)產(chǎn)者指向的隊(duì)列尾和消費(fèi)者指向的隊(duì)列頭是同個(gè)節(jié)點(diǎn)。于是,這兩個(gè)產(chǎn)者和消費(fèi)者之間樣會(huì)產(chǎn)鎖競(jìng)爭(zhēng)。在LinkedBlockingQueue上,這個(gè)鎖機(jī)制是通過(guò)synchronized這個(gè)Java關(guān)鍵字來(lái)實(shí)現(xiàn)的。般情況下,這個(gè)鎖最終會(huì)對(duì)應(yīng)到操作系統(tǒng)層的加鎖機(jī)制(OS based Lock),這個(gè)鎖機(jī)制需要由操作系統(tǒng)的內(nèi)核來(lái)進(jìn)裁決。這個(gè)裁決,也需要通過(guò)

4、次上下切換(Context Switch),把沒(méi)有拿到鎖的線程掛起等待。不知道你還記不記得,我們?cè)诘?8講講過(guò)的異常和中斷,這的上下切換要做的和異常和中斷的是樣的。上下切換的過(guò)程,需要把當(dāng)前執(zhí)線程的寄存器等等的信息,保存到線程棧。這個(gè)過(guò)程也必然意味著,已經(jīng)加載到速緩存的指令或者數(shù)據(jù),回到了主內(nèi)存,會(huì)進(jìn)步拖慢我們的性能。我們可以按照Disruptor介紹資料提到的Benchmark,寫段代碼來(lái)看看,是不是真是這樣的。這我放了段Java代碼,代碼的邏輯很簡(jiǎn)單,就是把個(gè)long類型的counter,從0增到5億。種式是沒(méi)有任何鎖,另外個(gè)式是每次增的時(shí)候都要去取個(gè)鎖。你可以在的電腦上試試跑下這個(gè)程序。

5、在我這,兩個(gè)式執(zhí)所需要的時(shí)間分別是207毫秒和9603毫秒,性能差出了將近50倍。package com.xuwenhao.perf.jmm;import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class LockBenchmarkpublic static void runIncrement()long counter = 0; long max = 5000000

6、00L;long start = System.currentTimeMillis(); while (counter < max) counter+;long end = System.currentTimeMillis();System.out.println("Time spent is " + (end start) + "ms without lock");public static void runIncrementWithLock()Lock lock = new ReentrantLock(); long counter = 0;l

7、ong max = 500000000L;long start = System.currentTimeMillis(); while (counter < max) if (lock.tryLock()counter+;lock.unlock();long end = System.currentTimeMillis();System.out.println("Time spent is " + (end start) + "ms with lock");public static void main(String args) runIncrem

8、ent();runIncrementWithLock();加鎖和不加鎖增counterTime spent is 207ms without lockTime spent is 9603ms with lock性能差出將近10倍鎖的RingBuer加鎖很慢,所以Disruptor的解決案就是“鎖”。這個(gè)“鎖”指的是沒(méi)有操作系統(tǒng)層的鎖。實(shí)際上,Disruptor還是利了個(gè)CPU硬件持的指令,稱之為CAS(Compare And Swap,較和交換)。 在Intel CPU,這個(gè)對(duì)應(yīng)的指令就是 cmpxchg。那么下,我們就起從Disruptor的源碼,到具體的硬件指令來(lái)看看這是怎么回事。Disr

9、uptor的RingBuer是這么設(shè)計(jì)的,它和直接在鏈表的頭和尾加鎖不同。Disruptor的RingBuer創(chuàng)建了個(gè)Sequence對(duì)象,來(lái)指向當(dāng)前的RingBuer的頭和尾。這個(gè)頭和尾的標(biāo)識(shí)呢,不是通過(guò)個(gè)指針來(lái)實(shí)現(xiàn)的,是通過(guò)個(gè)序號(hào)。這也是為什么對(duì)應(yīng)源碼的類名叫Sequence。在這個(gè)RingBuer當(dāng)中,進(jìn)產(chǎn)者和消費(fèi)者之間的協(xié)調(diào),采的是對(duì)序號(hào)的式。當(dāng)產(chǎn)者想要往隊(duì)列加新數(shù)據(jù)的時(shí)候,它會(huì)把當(dāng)前的產(chǎn)者的Sequence的序號(hào),加上需要加的新數(shù)據(jù)的數(shù)量,然后和實(shí)際的消費(fèi)者所在的位置進(jìn)對(duì),看看隊(duì)列是不是有夠的空間加這些數(shù)據(jù),還沒(méi)有處理完的數(shù)據(jù)。覆蓋掉消費(fèi)者在Sequence的代碼,就是通過(guò)compa

10、reAndSet這個(gè)法,并且最終調(diào)到了UNSAFE.compareAndSwapLong,也就是直接使了CAS指令。public boolean compareAndSet(final long expectedValue, final long newValue)return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue);public long addAndGet(final long increment)long currentValue; long newValue;docurrentValu

11、e = get();newValue = currentValue + increment;while (!compareAndSet(currentValue, newValue);return newValue;Sequence源碼中的addAndGet,如果CAS的操作沒(méi)有,它會(huì)不斷忙等待地重試這個(gè)CAS指令,也就是較和交換的操作,并不是基礎(chǔ)庫(kù)的個(gè)函數(shù)。它也不是操作系統(tǒng)實(shí)現(xiàn)的個(gè)系統(tǒng)調(diào),是個(gè)CPU硬件持的指令。在我們服務(wù)器所使的Intel CPU上,就是cmpxchg這個(gè)指令。compxchg ax (隱式參數(shù),EAX累加器), bx (源操作數(shù)地址), cx (標(biāo)操作數(shù)地址)cmpxch

12、g指令,共有三個(gè)操作數(shù),第個(gè)操作數(shù)不在指令出現(xiàn),是個(gè)隱式的操作數(shù),也就是EAX累加寄存器的值。第個(gè)操作數(shù)就是源操作數(shù),并且指令會(huì)對(duì)這個(gè)操作數(shù)和上的累加寄存器的值。如果值是相同的,那,CPU會(huì)把ZF(也就是條件碼寄存器零標(biāo)志位的值)設(shè)置為1,然后再把第三個(gè)操作數(shù)(也就是標(biāo)操作數(shù)),設(shè)置到源操作數(shù)的地址上。如果不相等的話,就會(huì)把源操作數(shù)的值,設(shè)置到累加器寄存器。我在這放了這個(gè)邏輯對(duì)應(yīng)的偽代碼,你可以看下。如果你對(duì)匯編指令、條件碼寄存器這些知識(shí)點(diǎn)有點(diǎn)模糊了,可以回頭去看看第5講、第6講關(guān)于匯編指令的部分。IF ax< = bx THEN ZF = 1, bx = cxELSE ZF = 0,

13、ax = bx單個(gè)指令是原的,這也就意味著在使CAS操作的時(shí)候,我們不再需要單獨(dú)進(jìn)加鎖,直接調(diào)就可以了。沒(méi)有了鎖,CPU這部速跑就像在賽道上駛,遇到需要上下切換這樣的紅燈停下來(lái)。雖然會(huì)遇到像CAS這樣復(fù)雜的來(lái)仍然會(huì)快很多。指令,就好像賽道上會(huì)有U型彎樣,不過(guò)全停下來(lái)等待,我們CPU運(yùn)起那么,CAS操作到底會(huì)有多快呢?我們還是段Java代碼來(lái)看下。package com.xuwenhao.perf.jmm;import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock;import jav

14、a.util.concurrent.locks.ReentrantLock;public class LockBenchmark public static void runIncrementAtomic()AtomicLong counter = new AtomicLong(0); long max = 500000000L;long start = System.currentTimeMillis(); while (counter.incrementAndGet() < max) long end = System.currentTimeMillis();System.out.p

15、rintln("Time spent is " + (end start) + "ms with cas");public static void main(String args) runIncrementAtomic();Time spent is 3867ms with cas和上的counter增樣,只不過(guò)這次,增我們采了AtomicLong這個(gè)Java類。的incrementAndGet最終到了CPU指令層,在實(shí)現(xiàn)的時(shí)候的就是CAS操作??梢钥吹?,它所花費(fèi)的時(shí)間, 雖然要沒(méi)有任何鎖的操作慢上個(gè)數(shù)量級(jí),但是起使ReentrantLock這樣的操作

16、系統(tǒng)鎖的機(jī)制,還是減少了半以上的時(shí)間??偨Y(jié)延伸好了,咱們專欄的正內(nèi)容到今天就要結(jié)束了。今天最后講,我?guī)е闫鹂戳薉isruptor代碼的個(gè)核設(shè)計(jì),也就是它的RingBuer是怎么做到鎖的。Java基礎(chǔ)庫(kù)的BlockingQueue,都需要通過(guò)顯地加鎖來(lái)保障產(chǎn)者之間、消費(fèi)者之間,乃產(chǎn)者和消費(fèi)者之間,發(fā)鎖的問(wèn)題。但是,加鎖會(huì)拖慢我們的性能。在獲取鎖過(guò)程中,CPU沒(méi)有去執(zhí)計(jì)算的相關(guān)指令,要等待操作系統(tǒng)進(jìn)鎖競(jìng)爭(zhēng)的裁決。那些沒(méi)有拿到鎖被掛起等待的線程,則需要進(jìn)上下切換。這個(gè)上下切換,會(huì)把掛起線程的寄存器的數(shù)據(jù)放到線程的程序棧去。這也意味著,加載到速緩存的數(shù)據(jù)也失效了,程序就變得更慢了。Disruptor

17、的RingBuer采了個(gè)鎖的解決案,通過(guò)CAS這樣的操作,去進(jìn)序號(hào)的增和對(duì),使得CPU不需要獲取操作系統(tǒng)的鎖。是能夠繼續(xù)順序地執(zhí)CPU指令。沒(méi)有上下切換、沒(méi)有操作系統(tǒng)鎖,然程序就跑得快了。不過(guò)因?yàn)椴闪薈AS這樣的忙等待(Busy Wait)的式,會(huì)使得我們的CPU始終滿負(fù)荷運(yùn)轉(zhuǎn),消耗的電,算是個(gè)的缺點(diǎn)。程序的CAS調(diào),到我們的CPU硬件層,就是個(gè)指令,這個(gè)指令就是cmpxchg??梢钥吹?,當(dāng)想要追求最極致的性能的時(shí)候,我們會(huì)從應(yīng)層、貫穿到操作系統(tǒng),乃最后的CPU硬件,搞清楚從級(jí)語(yǔ)到系統(tǒng)調(diào),乃最后的匯編指令,這整個(gè)過(guò)程是怎么執(zhí)代碼的。這個(gè),也是學(xué)習(xí)組成原理這專欄的意義所在。推薦閱讀不知道上講說(shuō)的

18、Disruptor相關(guān)材料,你有沒(méi)有讀完呢?如果沒(méi)有讀完的話,我建議你還是先去研讀下。如果你已經(jīng)讀完了,這再給你推薦些額外的閱讀材料,那就是著名的Implement Lock Free Queues這篇論。你可以更深地學(xué)習(xí)下,怎么實(shí)現(xiàn)個(gè)鎖隊(duì)列。課后思考最后,給你留道思考題。這道題有點(diǎn)難,不過(guò)也很有意思。請(qǐng)你閱讀下Disruptor開源庫(kù)的Sequence這個(gè)類的代碼,看看它和個(gè)普通的AtomicLong到底有什么區(qū)別,以及為什么它要這樣實(shí)現(xiàn)。歡迎在留區(qū)寫下你的思考和,和家起探討應(yīng)層和硬件層之間的關(guān)聯(lián)性。如果有收獲,你也可以把這篇章給你的朋友。精選留:姜 2019-09-09 18:36:10課

19、后問(wèn)題:對(duì)了Sequence與AtomicLong源碼,在于CAS之前,Disruptor直接取原有值,做CAS操作;AtomicL ong使了getLongVolatile獲值。兩者value都了Volatile來(lái)修飾,這點(diǎn)是共同的;但在于獲值時(shí),AtomicLong再次使volatile(getLo ngVolatile), 由于Volatile會(huì)在內(nèi)存中強(qiáng)刷新此值,相這樣就會(huì)增加 次性能的消耗,另外還有Spinlo ck(旋鎖),這兩點(diǎn)都會(huì)帶來(lái)性能消耗。為什么Disruptor可以這樣做,免掉getLongVolatile操作,我猜想應(yīng)該是緩存的填充,避免了偽共享(False Shari

20、ng)的問(wèn)題,線程對(duì)value值的修改,所以不需要再次強(qiáng)刷內(nèi)存的步驟。出現(xiàn)不同線程同時(shí)修改同緩存不同變量的問(wèn)題請(qǐng)師指正!/* Sequence關(guān)于取值是直接Get(), 查看內(nèi)部法直接返回value* Atomically add the supplied value.* param increment he value to add to the sequence.* return he value after the increment.*/public long addAndGet(nal long increment)long currentValue; long newValue;d

21、ocurrentValue = get();newValue = currentValue + increment;while (!compareAndSet(currentValue, newValue);return newValue;/* AtomicLong關(guān)于取值時(shí)使getLongVolatile,查看其源碼,增加了Volatile和旋鎖* getAndAddLong*/public nal long getAndAddLong(Object var1, long var2, long var4) long var6;do var6 = this.getLongVolatile(va

22、r1, var2); while(!pareAndSwapLong(var1, var2, var6, var6 + var4);return var6;/* getLongVolatile源碼(C+)* 1. volatile, 刷新內(nèi)存* 2.Spinlock, 旋鎖*/ jlongsun:misc:Unsafe:getLongVolatile (jobject obj, jlong oset)volatile jlong *addr = (jlong *) (char *) obj + oset); spinlock lock;return *addr; 3贊姜 2019-09-09 12:49:13LinkedBlockingQueue源碼(JDK1.8)看了下, 使的是ReentrantLock和Condition來(lái)現(xiàn)師說(shuō)的synchronized關(guān)鍵字, 是不是哪我忽略掉了? 1贊的,沒(méi)發(fā)陳華應(yīng) 2019-09-09 12:06:50專欄最開始就訂閱了,看到了這篇專

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論