很多情况下保持黑箱即可,因为打开这个黑箱,你会发现黑箱变成黑洞,吞噬你所有的时间和精力。有可能带你偏离原来的方向,陷入到不必要的细节中而无法自拔。 --
适度打开即可
CPU的制作过程
Intel cpu的制作过程
https://haokan.baidu.com/v?vid=11928468945249380709&pd=bjh&fr=bjhauthor&type=video
CPU是如何制作的(文字描述)
https://www.sohu.com/a/255397866_468626
CPU的原理
计算机需要解决的最根本问题:如何代表数字
晶体管是如何工作的:
https://haokan.baidu.com/v?vid=16026741635006191272&pd=bjh&fr=bjhauthor&type=video
晶体管的工作原理:
https://www.bilibili.com/video/av47388949?p=2
汇编语言的执行过程
汇编语言的本质:机器语言的助记符 其实它就是机器语言
计算机通电 -> CPU读取内存中程序(电信号输入)
->时钟发生器不断震荡通断电(GHZ/s) ->推动CPU内部一步一步执行
(执行多少步取决于指令需要的时钟周期)
->计算完成->写回(电信号)->写给显卡输出(sout,或者图形)
上述中:内存中数据不必写到cpu,而是直接由DMA处理(cpu告诉内存,把其某块的数据发送到显卡,DMA会通过数据总线将数据发送到显卡)
CPU的基本组成
- PC -> Program Counter 程序计数器 (记录当前指令地址)
- Registers -> 暂时存储CPU计算需要用到的数据
- ALU -> Arithmetic & Logic Unit 运算单元
- CU -> Control Unit 控制单元
- MMU -> Memory Management Unit 内存管理单元
- cache
缓存
一致性协议:https://www.cnblogs.com/z00377750/p/9180644.html
缓存行:折中都用64字节(程序的局部性原理,一次读取一块数据,而不是一个字节)
缓存行越大,局部性空间效率越高,但读取时间慢
缓存行越小,局部性空间效率越低,但读取时间快
英特尔采用的是以上缓存一致性协议
如果一个缓存行装载不下数据时,
总线锁
会介入,即一侧的核心处理完毕之后,另一侧才可以处理,这样效率会降低
上图中显示的是两个核心,
x
与y
处于一个cache line, 左边的核心要修改x
,从L1读取,发现不存在,依次从L2,L3读取,直到main memory才找到,然后依次在L3,L2,L1都缓存一份,最后计算单元进行处理;而右边的核心要操作y
,操作步骤与左边的核心相似,这样左右两边的核心各自持有一个cache line,但是操作的不同的数据,两个核心之间需要相互同步各自修改的,即伪共享
。
以下是模拟cache line 不是特别精确
public class T03_CacheLinePadding {
public static volatile long[] arr = new long[2];
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 10000_0000L; i++) {
arr[0] = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 10000_0000L; i++) {
arr[1] = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
public class T04_CacheLinePadding {
public static volatile long[] arr = new long[16];
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 10000_0000L; i++) {
arr[0] = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 10000_0000L; i++) {
arr[8] = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
缓存行对齐
:对于有些特别敏感的数字,会存在线程高竞争的访问,为了保证不发生伪共享,可以使用缓存行对齐的编程方式
-
JDK7中,很多采用long padding提高效率(让高竞争的数据永远处于一个缓存行且只有它自己,该方式适用于英特尔cpu)
如下:无论如何截取cursor这个数据所处的缓存行只有它自己
JDK8,加入了@Contended注解(实验)需要加上:JVM -XX:-RestrictContended
超线程
多组寄存器+PC。上图中为两组,减少了线程切换时,中间态数据的保存,这样同事状态两个线程的数据,ALU指向哪个执行就执行它
存储器的层次结构
越是往的成本越高,即塔尖的部分成本巨贵。至于CPU为什么设计成三层(L1,L2,L3)是由工业设计大量实践证明三层最合理
寄存器与memor速度比=1:100
一颗物理cpu内部有两个核心,这两个核心各自独立的L1 & L2,共享L3
两颗物理cpu共享主内存。
乱序执行
https://preshing.com/20120515/memory-reordering-caught-in-the-act/
禁止乱序
CPU层面:Intel -> 原语(mfence lfence sfence) 或者锁总线
JVM层级:8个hanppens-before原则 4个内存屏障 (LL LS SL SS)
as-if-serial : 不管硬件什么顺序,单线程执行的结果不变,看上去像是serial
举例说明(乱序执行可能产生的问题)
DCL单利为什么加volatile?
0 new - 这条指令开辟一块内存空间,并将m=0,并在本地方法栈内存放一个引用,指向给地址
3 dup - 将栈的引用复制一份
4 invokespecial #3 <T.<init>> - 从栈中弹出栈顶的那个引用指向那个对象执行构造方法,即 m = 8
7 astore_1 - 将弹出栈中最下面引用赋值给t
发生了指令重排之后,t=null, 则此时Thread 2 恰巧进入if 判断分支成立,即使用了半初始化状态的对象
详看2020-设计模式->>单利模式->>懒汉模式(Mgr06)
https://www.jianshu.com/p/9baf3170bff7
合并写(了解)
Write Combining Buffer
一般是4个字节
由于ALU速度太快,所以在写入L1的同时,写入一个WC Buffer,满了之后再直接更新到L2
NUMA(了解)
Non Uniform Memory Access
ZGC - NUMA aware
分配内存会优先分配该线程所在CPU的最近内存
从主板的结构来说,他会分为不同的插槽,每个插槽上有一组cpu和与之相邻的内存条,则cpu就近访问内存要比访问其他插槽的内存速度要快的多
————————————————————
坐标帝都,白天上班族,晚上是知识的分享者
如果读完觉得有收获的话,欢迎点赞加关注