剖析Disruptor:為什麼會這麼快?(二)神奇的快取行填充
原文地址:http://ifeve.com/disruptor-padding/
作者:Trisha 譯者:方騰飛 校對:丁一
我們經常提到一個短語Mechanical Sympathy,這個短語也是Martin部落格的標題(譯註:Martin Thompson),Mechanical Sympathy講的是底層硬體是如何運作的,以及與其協作而非相悖的程式設計方式。
我在上一篇文章中提到RingBuffer後,我們收到一些關於RingBuffer中填充快取記憶體行的評論和疑問。由於這個適合用漂亮的圖片來說明,所以我想這是下一個我該解決的問題了。
(譯註:Martin Thompson很喜歡用Mechanical Sympathy這個短語,這個短語源於賽車駕駛,它反映了駕駛員對於汽車有一種天生的感覺,所以他們對於如何最佳的駕御它非常有感覺。)
計算機入門
我喜歡在LMAX工作的原因之一是,在這裡工作讓我明白從大學和A Level Computing所學的東西實際上還是有意義的。做為一個開發者你可以逃避不去了解CPU,資料結構或者大O符號 —— 而我用了10年的職業生涯來忘記這些東西。但是現在看來,如果你知道這些知識並應用它,你能寫出一些非常巧妙和非常快速的程式碼。
因此,對在學校學過的人是種複習,對未學過的人是個簡單介紹。但是請注意,這篇文章包含了大量的過度簡化。
CPU是你機器的心臟,最終由它來執行所有運算和程式。主記憶體(RAM)是你的資料(包括程式碼行)存放的地方。本文將忽略硬體驅動和網路之類的東西,因為Disruptor的目標是儘可能多的在記憶體中執行。
CPU和主記憶體之間有好幾層快取,因為即使直接訪問主記憶體也是非常慢的。如果你正在多次對一塊資料做相同的運算,那麼在執行運算的時候把它載入到離CPU很近的地方就有意義了(比如一個迴圈計數-你不想每次迴圈都跑到主記憶體去取這個資料來增長它吧)。
越靠近CPU的快取越快也越小。所以L1快取很小但很快(譯註:L1表示一級快取),並且緊靠著在使用它的CPU核心。L2大一些,也慢一些,並且仍然只能被一個單獨的 CPU 核使用。L3在現代多核機器中更普遍,仍然更大,更慢,並且被單個插槽上的所有 CPU 核共享。最後,你擁有一塊主存,由全部插槽上的所有 CPU 核共享。
當CPU執行運算的時候,它先去L1查詢所需的資料,再去L2,然後是L3,最後如果這些快取中都沒有,所需的資料就要去主記憶體拿。走得越遠,運算耗費的時間就越長。所以如果你在做一些很頻繁的事,你要確保資料在L1快取中。
Martin和Mike的 QCon presentation演講中給出了一些快取未命中的消耗資料:
從CPU到 | 大約需要的 CPU 週期 | 大約需要的時間 |
主存 | 約60-80納秒 | |
QPI 匯流排傳輸 (between sockets, not drawn) |
約20ns | |
L3 cache | 約40-45 cycles, | 約15ns |
L2 cache | 約10 cycles, | 約3ns |
L1 cache | 約3-4 cycles, | 約1ns |
暫存器 | 1 cycle |
如果你的目標是讓端到端的延遲只有 10毫秒,而其中花80納秒去主存拿一些未命中資料的過程將佔很重的一塊。
快取行
現在需要注意一件有趣的事情,資料在快取中不是以獨立的項來儲存的,如不是一個單獨的變數,也不是一個單獨的指標。快取是由快取行組成的,通常是64位元組(譯註:這篇文章發表時常用處理器的快取行是64位元組的,比較舊的處理器快取行是32位元組),並且它有效地引用主記憶體中的一塊地址。一個Java的long型別是8位元組,因此在一個快取行中可以存8個long型別的變數。
非常奇妙的是如果你訪問一個long陣列,當陣列中的一個值被載入到快取中,它會額外載入另外7個。因此你能非常快地遍歷這個陣列。事實上,你可以非常快速的遍歷在連續的記憶體塊中分配的任意資料結構。我在第一篇關於ring buffer的文章中順便提到過這個,它解釋了我們的ring buffer使用陣列的原因。
因此如果你資料結構中的項在記憶體中不是彼此相鄰的(連結串列,我正在關注你呢),你將得不到免費快取載入所帶來的優勢。並且在這些資料結構中的每一個項都可能會出現快取未命中。
不過,所有這種免費載入有一個弊端。設想你的long型別的資料不是陣列的一部分。設想它只是一個單獨的變數。讓我們稱它為head
,這麼稱呼它其實沒有什麼原因。然後再設想在你的類中有另一個變數緊挨著它。讓我們直接稱它為tail
。現在,當你載入head
到快取的時候,你也免費載入了tail
。
聽想來不錯。直到你意識到tail
正在被你的生產者寫入,而head
正在被你的消費者寫入。這兩個變數實際上並不是密切相關的,而事實上卻要被兩個不同核心中執行的執行緒所使用。
設想你的消費者更新了head
的值。快取中的值和記憶體中的值都被更新了,而其他所有儲存head
的快取行都會都會失效,因為其它快取中head
不是最新值了。請記住我們必須以整個快取行作為單位來處理(譯註:這是CPU的實現所規定的,詳細可參見深入分析Volatile的實現原理),不能只把head
標記為無效。
現在如果一些正在其他核心中執行的程式只是想讀tail
的值,整個快取行需要從主記憶體重新讀取。那麼一個和你的消費者無關的執行緒讀一個和head
無關的值,它被快取未命中給拖慢了。
當然如果兩個獨立的執行緒同時寫兩個不同的值會更糟。因為每次執行緒對快取行進行寫操作時,每個核心都要把另一個核心上的快取塊無效掉並重新讀取裡面的資料。你基本上是遇到兩個執行緒之間的寫衝突了,儘管它們寫入的是不同的變數。
這叫作“偽共享”(譯註:可以理解為錯誤的共享),因為每次你訪問head
你也會得到tail
,而且每次你訪問tail
,你也會得到head
。這一切都在後臺發生,並且沒有任何編譯警告會告訴你,你正在寫一個併發訪問效率很低的程式碼。
解決方案-神奇的快取行填充
你會看到Disruptor消除這個問題,至少對於快取行大小是64位元組或更少的處理器架構來說是這樣的(譯註:有可能處理器的快取行是128位元組,那麼使用64位元組填充還是會存在偽共享問題),通過增加補全來確保ring buffer的序列號不會和其他東西同時存在於一個快取行中。
1 |
public long p1,
p2, p3, p4, p5, p6, p7; //
cache line padding |
2 |
private volatile long cursor
= INITIAL_CURSOR_VALUE; |
3 |
public long p8,
p9, p10, p11, p12, p13, p14; //
cache line padding |
因此沒有偽共享,就沒有和其它任何變數的意外衝突,沒有不必要的快取未命中。
在你的Entry
類中也值得這樣做,如果你有不同的消費者往不同的欄位寫入,你需要確保各個欄位間不會出現偽共享。
修改:Martin寫了一個從技術上來說更準確更詳細的關於偽共享的文章,並且釋出了效能測試結果。
相關文章
- MYSQL索引為什麼這麼快?瞭解索引的神奇之處MySql索引
- Nginx 為什麼這麼快?Nginx
- Redis為什麼這麼快?Redis
- 為什麼Julia這麼快?
- 為什麼redis是單執行緒的以及為什麼這麼快?Redis執行緒
- 快速排序為什麼這麼快?排序
- redis是單執行緒的,為什麼這麼快Redis執行緒
- Redis是單執行緒的,但Redis為什麼這麼快?Redis執行緒
- 比Redis快5倍的中介軟體,究竟為什麼這麼快?Redis
- Netty是什麼,Netty為什麼速度這麼快,執行緒模型分析Netty執行緒模型
- 快取穿透、快取雪崩和快取擊穿是什麼?快取穿透
- 什麼是redis快取雪崩、快取穿透、快取擊穿Redis快取穿透
- 什麼是redis的快取雪崩與快取穿透Redis快取穿透
- 為什麼要用Redis?Redis為什麼這麼快?(來自知乎)Redis
- 為什麼要使用Redis做快取Redis快取
- 快取和web快取分別是什麼?快取Web
- 京東二面,Redis為什麼那麼快?Redis
- 碾壓Python!為什麼Julia速度這麼快?Python
- [譯][A crash course in WebAssembly] 為什麼WebAssembly這麼快Web
- Redis 為什麼這麼快?這才是最完美的回答Redis
- redis為什麼快Redis
- Kafka 為什麼快Kafka
- 來說說快取穿透、快取擊穿、快取雪崩都是什麼?怎麼解決?快取穿透
- 什麼是DNS快取?DNS快取有哪些作用?DNS快取
- 快遞都比外賣快了?快遞按下“加速鍵” 今年雙11快遞為什麼這麼快?
- Redis為什麼那麼快?Redis
- 為什麼V8引擎這麼快?
- 硬核!15張圖解Redis為什麼這麼快圖解Redis
- 面試官:快取穿透、快取雪崩和快取擊穿是什麼?面試快取穿透
- 為什麼Redis這麼快?5分鐘成為Redis高手Redis
- 代理快取有什麼弊端?快取
- Kafka為什麼速度那麼快?Kafka
- 如何定期清理DNS快取?清理DNS快取有什麼用?DNS快取
- Android 圖片載入快取問題:為什麼你的Glide快取沒有起作用?Android快取IDE
- 什麼是HTTP快取機制?HTTP快取
- Kafka為什麼效能這麼快?4大核心原因詳解Kafka
- 告訴你MySQL主鍵查詢為什麼這麼快MySql
- 破玩意 | Redis 為什麼那麼快Redis
- mybatis快取-二級快取MyBatis快取