首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 開發者必懂的 CPU 快取原理:從緩存行、偽共享到 L1 Miss 實測優化

     編輯:Java 相關知識

前言:高級語言同樣需要理解硬體

許多 Java 開發者以為 CPU 快取屬於 C/C++ 領域,直到遇到 LMAX Disruptor、Martin Fowler 文章才發現:資料在記憶體中的佈局直接決定 Java 程式效能。本文聚焦 CPU 快取對 Java 的影響,不涉及硬體電路細節。

現代 CPU 三級快取架構

層級容量速度範圍
L1最小 (32-64 KB)最快 (~3-4 cycles, 0.5-1 ns)每核心獨有(分 L1d 數據 / L1i 指令)
L2中等 (256 KB - 1 MB)次之 (~10-20 cycles, 3-7 ns)每核心獨有
L3最大 (8-32 MB)最慢 (~40-45 cycles, 15 ns)同插槽核心共享
主記憶體GB 級~120-240 cycles (60-120 ns)全系統共享

CPU 依序尋找 L1→L2→L3→記憶體,路徑越長延遲越高。目標:讓熱點資料盡可能停留在 L1。

關鍵概念:緩存行

  • 快取以 緩存行 為最小存取單位,典型大小 64 位元組(可用 cpuid 或 /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size 查看)。
  • 讀取一個 long (8 bytes) 會順帶把相鄰 56 bytes 載入同一緩存行。
  • 善用空間局部性:陣列連續存取、物件欄位緊湊排列,可大幅提高命中率。

實測:二維陣列遍歷順序對 L1 Miss 的驚人影響

測試環境

  • CPU:Xeon E3(64 位元)
  • JVM:64 位元
  • 陣列:long[1024*1024][6] → 每列 16 bytes (物件頭) + 6×8 = 64 bytes = 剛好一條緩存行

程式碼對比


// 快:行優先(符合記憶體佈局)
for (int i = 0; i < DIM1; i++)
    for (int j = 0; j < DIM2; j++)
        sum += arr[i][j];

// 慢:列優先(踩地雷)
for (int j = 0; j < DIM2; j++)
    for (int i = 0; i < DIM1; i++)
        sum += arr[i][j];

結果

  • 行優先:~1,000 微秒
  • 列優先:~10,000 微秒(10 倍差距)

原因:列優先每次存取不同列同欄,導致剛載入的緩存行(含 6 個 long)僅用到 1 個就被擠出,造成大量 L1 Miss。

三大快取失效類型與對策

  1. 強制失效:首次存取資料。→ 預取(Prefetch、資料結構預熱)。
  2. 容量失效:工作集超過快取容量。→ 縮減資料結構、分塊演算法(Blocking/Tiling)、避免建立巨大中間物件。
  3. 衝突失效(偽共享):多執行緒修改同一緩存行內不同變數,導致緩存行在核心間來回傳輸。→ 填充對齊(@Contended、手動填充 long[7])、ThreadLocal、Disruptor RingBuffer 設計。

Java 實務優化清單

  • 熱點陣列採用一維扁平化或行優先存取。
  • 高併發計數器用 LongAdder(分段計數減少緩存行競爭)而非 AtomicLong。
  • 頻繁共讀少寫的資料結構考慮 StampedLock 樂觀讀或 CopyOnWriteArrayList。
  • 物件佈局:將常一起存取的欄位放在一起(JVM 有壓縮指標、欄位重排優化,但邏輯分組仍有幫助)。
  • 使用 -XX:+PrintCompilation -XX:+PrintInlining 觀察 JIT 行為,結合 perf、VTune 分析硬體計數器。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved