現代計算機一般由CPU
,以及主板、內存、硬盤等主要硬件結構組成,而決定計算機性能的最核心部件是CPU
+內存,CPU
負責處理程序指令,內存負責存儲指令執行結果。在這個工做機制當中CPU
的讀寫效率實際上是遠遠高於內存的,爲提高執行效率減小CPU
與內存的交互,通常在CPU
上設計了緩存結構,常見的爲三級緩存結構:java
L1 Cache,分爲數據緩存和指令緩存,邏輯核獨佔shell
L2 Cache,物理核獨佔,邏輯核共享數組
L3 Cache,全部物理核共享緩存
下圖爲CPU-Core(TM)I7-10510U
型號緩存結構服務器
存儲器存儲空間大小:內存>L3>L2>L1>寄存器。數據結構
存儲器速度快慢排序:寄存器>L1>L2>L3>內存。多線程
[root@192 ~]# getconf -a|grep CACHE LEVEL1_ICACHE_SIZE 32768 #L1緩存大小 LEVEL1_ICACHE_ASSOC 8 #L1緩存行大小 LEVEL1_ICACHE_LINESIZE 64 LEVEL1_DCACHE_SIZE 32768 LEVEL1_DCACHE_ASSOC 8 LEVEL1_DCACHE_LINESIZE 64 LEVEL2_CACHE_SIZE 262144 #L2緩存大小 LEVEL2_CACHE_ASSOC 4 LEVEL2_CACHE_LINESIZE 64 #L2緩存行大小 LEVEL3_CACHE_SIZE 8388608 #L3緩存大小 LEVEL3_CACHE_ASSOC 16 LEVEL3_CACHE_LINESIZE 64 #L3緩存行大小 LEVEL4_CACHE_SIZE 0 LEVEL4_CACHE_ASSOC 0 LEVEL4_CACHE_LINESIZE 0 [root@192 ~]# cat /proc/cpuinfo |grep -i cache cache size : 8192 KB cache_alignment : 64 cache size : 8192 KB cache_alignment : 64
JAVA程序毫無疑問也必須是運行在硬件機器之上,如何利用底層硬件工做原理,提高性能也必然是咱們須要考慮的,筆者今天以無鎖併發高性能框架Disruptor
爲例分析如何高效的利用CPU緩存。架構
Disruptor是一個開源框架,研發的初衷是爲了解決高併發下隊列鎖的問題,最先由LMAX(一種新型零售金融交易平臺)提出並使用,可以在無鎖的狀況下實現隊列的併發操做,並號稱可以在一個線程裏每秒處理6百萬筆訂單。併發
下方示例爲Disruptor
框架的內部代碼:框架
abstract class RingBufferPad { protected long p1, p2, p3, p4, p5, p6, p7; }
分析:
p1-p7僅用來填充緩存行,咱們跟本用不到它,可是咱們爲何要填充滿一個緩存行呢?
CPU在加載數據的時候,會把這個數據從內存加載到CPU Cache裏面
此時,CPU Cache裏面除了這個數據,還會加載這個數據先後定義的其餘變量
Disruptor是一個多線程的服務器框架,在這個數據先後定義的其餘變量,可能會被多個不一樣的線程去更新數據,讀取數據
若是常量的緩存失效,當再次讀取這個值的時候,須要從新從內存讀取,讀取速度會大大變慢
abstract class RingBufferPad { protected long p1, p2, p3, p4, p5, p6, p7; } abstract class RingBufferFields<E> extends RingBufferPad { ... private final long indexMask; private final Object[] entries; protected final int bufferSize; protected final Sequencer sequencer; ... } public final class RingBuffer<E> extends RingBufferFields<E> implements Cursored, EventSequencer<E>, EventSink<E> { ... protected long p1, p2, p3, p4, p5, p6, p7; ... }
RingBufferFields
裏面定義的變量先後分別定義了7個long類型的變量
RingBufferPad
,後面7個直接定義在RingBuffer
類中RingBufferFields
裏面定義的變量都是final
的,第一次寫入以後就不會再進行修改
LinkedBlockingQueue
,比起Disruptor的RingBuffer要慢不少,主要緣由
LinkedBlockingQueue
對於鎖的依賴
LinkedBlockingQueue
的鎖機制是經過ReentrantLock
,須要JVM進行裁決
Sequence
對象,用來指向當前的RingBuffer的頭和尾
cmpxchg
指令:compxchg [ax] (隱式參數,EAX累加器), [bx] (源操做數地址), [cx] (目標操做數地址)
IF [ax]== [bx] THEN [ZF] = 1, [bx] = [cx] ELSE [ZF] = 0, [ax] = [bx]
Sequence關鍵代碼
以下:
public long addAndGet(final long increment) { long currentValue; long newValue; // 若是CAS操做沒有成功,會不斷等待重試 do { currentValue = get(); newValue = currentValue + increment; } while (!compareAndSet(currentValue, newValue)); return newValue; } public boolean compareAndSet(final long expectedValue, final long newValue) { // 調用CAS指令 return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue); }
互斥鎖競爭、CAS樂觀鎖與無鎖測試:
public class LockBenchmark { private static final long MAX = 500_000_000L; private static void runIncrement() { long counter = 0; long start = System.currentTimeMillis(); while (counter < MAX) { counter++; } long end = System.currentTimeMillis(); System.out.println("Time spent is " + (end - start) + "ms without lock"); } private static void runIncrementWithLock() { Lock lock = new ReentrantLock(); long counter = 0; 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"); } private static void runIncrementAtomic() { AtomicLong counter = new AtomicLong(0); long start = System.currentTimeMillis(); while (counter.incrementAndGet() < MAX) { } long end = System.currentTimeMillis(); System.out.println("Time spent is " + (end - start) + "ms with cas"); } public static void main(String[] args) { runIncrement(); runIncrementWithLock(); runIncrementAtomic(); // Time spent is 153ms without lock // Time spent is 7801ms with lock // Time spent is 3164ms with cas // 7801 / 153 ≈ 51 // 3164 / 153 ≈ 21 } }得出
** 結論:無鎖性能要遠高於cas與lock,cas要大於lock**
更多好文章,請關注公衆號:奇客時間,原創JAVA架構技術棧社區