原理
程序在执行时呈现出局部性规律,即在一段时间内,整个程序的执行仅限于程序中的某一部分。执行所访问的存储空间也局限于某个内存区域。这就是程序的局部,因为这一规律,所以在设计计算机系统时会使用该原理来优化程序执行。
同时,在设计整个计算机系统时,为了解决磁盘-内存、内存-寄存器速度不匹配的问题,加入了缓存的概念,今天要分享的问题出现在内存-寄存器之间缓存上面。在传统的64位CPU中,存在一个缓存行的概念,即程序会将连续的64字节内容load到CPU高速缓存中,提升CPU执行数据处理的速度。
但是,现在主流的CPU都是多核心(超核)的处理器,每一个核心可以同时执行一个线程,那么计算机在同一时刻就可以执行多条线程(中的指令),由于程序的局部性原理,当多个线程在执行时需要处理物理位置相近的数据时,就会同时将位于同一个缓存行的数据load到缓冲中,多条线程处理了数据时会发现缓存失效的问题,重新从内存中load数据,造成多个线程相互影响,形成伪共享。
Java 代码
下面给出Java代码重现伪共享问题的实例:
package com.zjw.cache_line;
/**
* @author zjwblog <cn.zjwblog@gmail.com>
* @version 1.0
*/
public class MainTest {
private static final long COUNT = 1000_0000;
private static class Super {
// 进行缓存行填充,让程序load的数据在不同的缓存中
public volatile long p1, p2, p3, p4, p5, p6, p7;
}
private static class Test1 {
public volatile long x = 0;
}
private static class Test2 extends Super {
public volatile long x = 0;
}
public static Test1[] arr1 = new Test1[2];
public static Test2[] arr2 = new Test2[2];
static {
arr1[0] = new Test1();
arr1[1] = new Test1();
arr2[0] = new Test2();
arr2[1] = new Test2();
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100; i++) {
test1();
test2();
}
}
public static final void test1() throws Exception {
Thread t1 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
arr1[0].x++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
arr1[1].x++;
}
});
final long startTime = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
final long stopTime = System.nanoTime();
System.out.println("Example 1 run [" + ((stopTime - startTime) / 1000 / 1000) + "] ms");
}
public static final void test2() throws Exception {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000_0000; i++) {
arr2[0].x++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000_0000; i++) {
arr2[1].x++;
}
});
final long startTime = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
final long stopTime = System.nanoTime();
System.out.println("Example 2 run [" + ((stopTime - startTime) / 1000 / 1000) + "] ms");
}
}
执行结果:
Example 1 run [342] ms
Example 2 run [79] ms
Example 1 run [333] ms
Example 2 run [79] ms
Example 1 run [335] ms
Example 2 run [77] ms
Example 1 run [342] ms
Example 2 run [74] ms
...
Disruptor使用到了该理论
Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,里面有一个环形队列,使用到了该理论:
abstract class RingBufferPad {
protected long p1, p2, p3, p4, p5, p6, p7;
}
以及RingBuffer内部定义了缓存对齐的代码
public final class RingBuffer<E> extends RingBufferFields<E> implements Cursored, EventSequencer<E>, EventSink<E> {
public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE;
protected long p1, p2, p3, p4, p5, p6, p7;
/**
* Construct a RingBuffer with the full option set.
*
* @param eventFactory to newInstance entries for filling the RingBuffer
* @param sequencer sequencer to handle the ordering of events moving through the RingBuffer.
* @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2
*/
RingBuffer(
EventFactory<E> eventFactory,
Sequencer sequencer)
{
super(eventFactory, sequencer);
}
// ... 一些额外的方法
}