现代计算机一般由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架构技术栈社区