寫Java也得了解CPU快取
本文由碼農網 – 小峰原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃!
CPU,一般認為寫C/C++的才需要了解,寫高階語言的(Java/C#/pathon…)並不需要了解那麼底層的東西。我一開始也是這麼想的,但直到碰到LMAX的Disruptor,以及馬丁的博文,才發現寫Java的,更加不能忽視CPU。經過一段時間的閱讀,希望總結一下自己的閱讀後的感悟。本文主要談談CPU快取對Java程式設計的影響,不涉及具體CPU快取的機制和實現。
現代CPU的快取結構一般分三層,L1,L2和L3。如下圖所示:
級別越小的快取,越接近CPU, 意味著速度越快且容量越少。
L1是最接近CPU的,它容量最小,速度最快,每個核上都有一個L1 Cache(準確地說每個核上有兩個L1 Cache, 一個存資料 L1d Cache, 一個存指令 L1i Cache);
L2 Cache 更大一些,例如256K,速度要慢一些,一般情況下每個核上都有一個獨立的L2 Cache;
L3 Cache是三級快取中最大的一級,例如12MB,同時也是最慢的一級,在同一個CPU插槽之間的核共享一個L3 Cache。
當CPU運作時,它首先去L1尋找它所需要的資料,然後去L2,然後去L3。如果三級快取都沒找到它需要的資料,則從記憶體裡獲取資料。尋找的路徑越長,耗時越長。所以如果要非常頻繁的獲取某些資料,保證這些資料在L1快取裡。這樣速度將非常快。下表表示了CPU到各快取和記憶體之間的大概速度:
從CPU到 大約需要的CPU週期 大約需要的時間(單位ns)
暫存器 1 cycle
L1 Cache ~3-4 cycles ~0.5-1 ns
L2 Cache ~10-20 cycles ~3-7 ns
L3 Cache ~40-45 cycles ~15 ns
跨槽傳輸 ~20 ns
記憶體 ~120-240 cycles ~60-120ns
利用CPU-Z可以檢視CPU快取的資訊:
在linux下可以使用下列命令檢視proc檔案系統或者sys下的裝置描述。
有了上面對CPU的大概瞭解,我們來看看快取行(Cache line)。快取,是由快取行組成的。一般一行快取行有64位元組(由上圖”64-byte line size”可知)。所以使用快取時,並不是一個一個位元組使用,而是一行快取行、一行快取行這樣使用;換句話說,CPU存取快取都是按照一行,為最小單位操作的。
這意味著,如果沒有好好利用快取行的話,程式可能會遇到效能的問題。可看下面的程式:
public class L1CacheMiss { private static final int RUNS = 10; private static final int DIMENSION_1 = 1024 * 1024; private static final int DIMENSION_2 = 6; private static long[][] longs; public static void main(String[] args) throws Exception { Thread.sleep(10000); longs = new long[DIMENSION_1][]; for (int i = 0; i < DIMENSION_1; i++) { longs[i] = new long[DIMENSION_2]; for (int j = 0; j < DIMENSION_2; j++) { longs[i][j] = 0L; } } System.out.println("starting...."); long sum = 0L; for (int r = 0; r < RUNS; r++) { final long start = System.nanoTime(); //slow // for (int j = 0; j < DIMENSION_2; j++) { // for (int i = 0; i < DIMENSION_1; i++) { // sum += longs[i][j]; // } // } //fast for (int i = 0; i < DIMENSION_1; i++) { for (int j = 0; j < DIMENSION_2; j++) { sum += longs[i][j]; } } System.out.println((System.nanoTime() - start)); } } }
以我所使用的Xeon E3 CPU和64位作業系統和64位JVM為例,如這裡所說,假設編譯器採用行主序儲存陣列。
64位系統,Java陣列物件頭固定佔16位元組(未證實),而long型別佔8個位元組。所以16+8*6=64位元組,剛好等於一條快取行的長度:
如32-36行程式碼所示,每次開始內迴圈時,從記憶體抓取的資料塊實際上覆蓋了longs[i][0]到longs[i][5]的全部資料(剛好64位元組)。因此,內迴圈時所有的資料都在L1快取可以命中,遍歷將非常快。
假如,將32-36行程式碼註釋而用25-29行程式碼代替,那麼將會造成大量的快取失效。因為每次從記憶體抓取的都是同行不同列的資料塊(如longs[i][0]到longs[i][5]的全部資料),但迴圈下一個的目標,卻是同列不同行(如longs[0][0]下一個是longs[1][0],造成了longs[0][1]-longs[0][5]無法重複利用)。執行時間的差距如下圖,單位是微秒(us):
最後,我們都希望需要的資料都在L1快取裡,但事實上經常事與願違,所以快取失效 (Cache Miss)是常有的事,也是我們需要避免的事。
一般來說,快取失效有三種情況:
1. 第一次訪問資料, 在cache中根本不存在這條資料, 所以cache miss, 可以通過prefetch解決。
2. cache衝突, 需要通過補齊來解決(偽共享的產生)。
3. cache滿, 一般情況下我們需要減少操作的資料大小, 儘量按資料的物理順序訪問資料。
譯文連結:http://www.codeceo.com/article/java-cpu-cache.html
翻譯作者:碼農網 – 小峰
[ 轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]
相關文章
- CPU快取快取
- JAVA 拾遺 — CPU Cache 與快取行Java快取
- java的Integer中也會有快取Java快取
- 從CPU快取看快取的套路快取
- 用Java寫一個分散式快取——快取管理Java分散式快取
- Java中volatile副作用:不使用CPU快取Java快取
- CPU快取記憶體快取記憶體
- 個人總結-CPU快取快取
- CPU快取是什麼?一二三級快取哪個對CPU最重要?快取
- 多核cpu、cpu快取記憶體、快取一致性協議、快取行、記憶體快取記憶體協議
- 用Java寫一個分散式快取——快取淘汰演算法Java分散式快取演算法
- printf重寫,可存入檔案、也可存入快取buff快取
- 談談CPU快取記憶體快取記憶體
- CPU快取和記憶體屏障快取記憶體
- CPU快取重新整理的誤解快取
- 快取淘汰、快取穿透、快取擊穿、快取雪崩、資料庫快取雙寫一致性快取穿透資料庫
- Java快取EhcacheJava快取
- Java快取--JCSJava快取
- Java 雙快取Java快取
- 快取,確實很香,卻也很受傷!快取
- 解讀CPU快取,它們如何工作的?快取
- 檢查LINUX環境的CPU快取Linux快取
- 淺談快取寫法(一):快取的雪崩和穿透快取穿透
- Java高併發快取架構,快取雪崩、快取穿透之謎Java快取架構穿透
- Effective C# 要點小結,不懂也得寫C#
- CPU快取學習及C6678快取使用總結(知識歸納)快取
- 用Java寫一個分散式快取——RESP服務端Java分散式快取服務端
- 修改Ehcache快取中取到的值,快取中的值也被修改了快取
- Java快取淺析Java快取
- CPU快取一致性協議MESI,memory barrier和java volatile快取協議Java
- 芯鮮Discovery | CPU來加速,AI學習也能快又準!AI
- CPU快取一致性整理筆記快取筆記
- 七個例子幫你更好地理解 CPU 快取快取
- 開啟CPU二級快取,提高系統效能快取
- Java Integer的快取策略Java快取
- LRU快取實現(Java)快取Java
- Java快取備忘大全Java快取
- Java記憶體快取-通過Google Guava建立快取Java記憶體快取GoGuava