上一篇:029-JVM-volatil e和指令重排
https://yuhongliang.blog.csdn.net/article/details/111997753
1.存储器的层次就结构
由于寄存器的速度是非常快的,是内存的100被,是硬盘的10的六次方倍。
下图是个硬件的速度指标,可以使我们对其有更为直观的认识:
从cpu到 | 大约需要cpu周期 | 大约小的时间 |
---|---|---|
主存(内存) | 60-80ns | |
QPI总线(between sockets ,ont drawn) | 20ns | |
L3 | 45cycle | 15ns |
L2 | 10cycle | 3ns |
L1 | 3cycle | 1ns |
寄存器 | 1cycle |
cpu读取数据,吸纳从寄存器中读取,如果无,则一次L1\L2\L3读取。
2.cache line 缓存行
那么系统读取数据是需要什么读取什么吗?当然是的,但是由于缓存行的存在,他会都去更多的数据。比如读取一个int类型4个字节的数据,他会把这四个字节后面的60个字节都读进去,即每次读64个字节的数据。这就是缓存行cache line
3.cache line 缓存行
场景:
在多核cpu读取数据的时候:
- core1 读取了x=1的数据,不好意思,由于cache line的存在,他需要把后面的y=2联通后面的数据一读进core1中;
- core2 读取了y=2的数据,不好意思,由于cache line的存在,他需要把后面的x=1联通后面的数据一读进core2中;
此时core1 对x=1做了运算是的x=11
此时core2 对y=2做了运算是的x=22
下你在在core1和core2中的数据如下:
我们发现出现了数据不一致的问题。关于数据不一致问题,这就是伪共享的问题
。
4.缓存对齐可以提高效率
知道了cache line的存在,我们在写代码的时候可以利用缓存对齐(就是每次64字节)的方式去提供效率,真的可以吗?
如果每次不是64个字节,需要等待到了64个字节才会写入缓存哦!中间有等待的时间,是的效率下降。
4.1 伪共享时下来低
代码如下:
package com.yuhl.c2020.cacheline;
/**
* @author yuhl
* @Date 2021/1/2 16:04
* @Classname One
* @Description cacheline在同一个cup中 效率低
*/
public class One {
private static class T {
public volatile long x = 0L;
}
//含有两个Long类型的数据,每个8个字节,大概率会加载到同一个cpu中。
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws Exception {
//线程t1修改第一个long 在core1中 由于一致性的存在,需要一个机制做一致性,影响效率
Thread t1 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++) {
arr[0].x = i;
}
});
//线程t2修改第一个long 在core2中 由于一致性的存在,需要一个机制做一致性,影响效率
Thread t2 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++) {
arr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
4.2 缓存对齐时下来高,不存在伪共享
直观看高了3倍
代码如下:
package com.yuhl.c2020.cacheline;
/**
* @author yuhl
* @Date 2021/1/2 16:05
* @Classname Two
* @Description cahcheline不在同一个cup中,效率更高
*/
public class Two {
private static class Padding {
public volatile long p1, p2, p3, p4, p5, p6, p7;
}
//继承Padding ,内衣金含有p1, p2, p3, p4, p5, p6, p7 56个字节的数据了,再加上 long x 大概路占用了一个缓存行,
//线程2 按到T的时候前面也有p1, p2, p3, p4, p5, p6, p7 56个字节的数据了,后面才是long x 所以 一定不存在为共享的问题,也就不存在某机制保证
//一致性的问题了。效率会高
private static class T extends Padding {
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++) {
arr[0].x = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++) {
arr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
5.伪共享问题引出数据一致性问题
上面提到了伪共享时会造成数据不一致问题,那么为了保证一致性我们应该怎么办么?目前x86架构下有哪些解决方案?
6.数据一致性问题解决方案
针对一致性的问题有两种解决方案:
- 总线锁:在L3和L2直接加锁,拿到锁才能处理下面工作。
- 一致性协议MESI(缓存一致性协议)
即通过四个状态来保证数据的一致性:
状态 | 解释 |
---|---|
modify | 更改过标注 |
exclusive | 独享 |
shared | 共享 |
invalid | 我读取是被别的core改动过标记,最终需要我再次同步下 |
关于缓存一致性的详细信息可以参考:https://blog.csdn.net/yahoo1994/article/details/108485859