用html5做手机网站,公司注册网站有安全风险怎么注销,软件开发需要学什么专业好,网站统计分析并发编程首先需要简单了解下现代CPU相关知识。通过一些简单的图#xff0c;简单的代码#xff0c;来认识CPU以及一些常见的问题。 目录 CPU存储与缓存的引入常见的三级缓存结构缓存一致性协议MESI协议缓存行 cache line 通过代码实例认识缓存行的重要性 CPU指令的乱序执行通过…并发编程首先需要简单了解下现代CPU相关知识。通过一些简单的图简单的代码来认识CPU以及一些常见的问题。 目录 CPU存储与缓存的引入常见的三级缓存结构缓存一致性协议MESI协议缓存行 cache line 通过代码实例认识缓存行的重要性 CPU指令的乱序执行通过代码实例认识到CPU指令乱序执行 CPU存储与缓存的引入
下图描述了存储器金字塔层次结构 正因为CPU的计算速度与内存速度的严重不匹配所以加上了多级缓存让CPU能执行更多的指令
常见的三级缓存结构
如下图描述一个有2个CPU且多核心的
L1高速缓存也叫一级缓存。一般内置在内核旁边是与CPU结合最为紧密的CPU缓存。一次访问只需要2~4个时钟周期L2高速缓存也叫二级缓存。空间比L1缓存大速度比L1缓存略慢。一次访问约需要10多个时钟周期L3高速缓存也叫三级缓存。部分单CPU多核心的才会有的缓存介于多核和内存之间。存储空间已达Mb级别一次访问约需要数十个时钟周期。
当CPU要读取一个数据时首先从L1缓存查找命中则返回若未命中再从L2缓存中查找如果还没有则从L3缓存查找如果有L3缓存的话。如果还是没有则从内存中查找并将读取到的数据逐级放入缓存。如下图所示 缓存一致性协议
因为现代CPU的架构所以必然会遇到多个处理器都涉及同一块主内存区域的更改时这就将导致各自的缓存数据不一致所以需要采取一定的规范来解决这个问题。如下图所示 总线锁是把CPU和内存的通信给锁住了使得在锁定期间其它处理器不能操作内存的其它数据这样开销较大 缓存锁不需锁定总线只需要锁定被缓存的共享对象实际为缓存行即可接受到lock指令通过缓存一致性协议维护本处理器内部缓存和其它处理器缓存的一致性。相比总线锁会提高CPU利用率。
MESI协议
MESI协议是基于Invalidate的高速缓存一致性协议并且是支持回写高速缓存的最常用协议之一。
MESI 是指4种状态的首字母。每个缓存行(Cache Line)有4个状态可用2个bit表示它们分别是
状态描述监听任务M 修改 (Modified)该Cache line有效数据被修改了和内存中的数据不一致数据只存在于本Cache中。缓存行必须时刻监听所有试图读该缓存行相对就主存的操作这种操作必须在缓存将该缓存行写回主存并将状态变成S共享状态之前被延迟执行。E 独享、互斥 (Exclusive)该Cache line有效数据和内存中的数据一致数据只存在于本Cache中。缓存行也必须监听其它缓存读主存中该缓存行的操作一旦有这种操作该缓存行需要变成S共享状态。S 共享 (Shared)该Cache line有效数据和内存中的数据一致数据存在于很多Cache中。缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求并将该缓存行变成无效Invalid。I 无效 (Invalid)该Cache line无效。无
当某个cpu修改缓存行数据时其它的cpu通过监听机制获悉共享缓存行的数据被修改会使其共享缓存行失效。本cpu会将修改后的缓存行写回到主内存中。此时其它的cpu如果需要此缓存行共享数据则从主内存中重新加载并放入缓存以此完成了缓存一致性。
缓存行 cache line 程序局部性原理这里解释为访问内存或缓存的某个位置顺带的把紧邻的位置一起读取出来 缓存行越大局部性空间效率越高但读取时间慢缓存行越小局部性空间效率越低但读取时间快
常见的缓存行一般64字节
通过代码实例认识缓存行的重要性
import java.util.concurrent.CountDownLatch;public class Main {private static class T {public volatile long x;}public static T[] arr new T[2];static {arr[0] new T();arr[1] new T();}// 一亿次static int FOR_COUNT 100_000_000;public static void main(String[] args) throws Exception{CountDownLatch latch new CountDownLatch(2);Thread t1 new Thread(()-{for (int i 0; i FOR_COUNT; i ){arr[0].x ;}latch.countDown();});Thread t2 new Thread(()-{for (int i 0; i FOR_COUNT; i ){arr[1].x ;}latch.countDown();});final long start System.nanoTime();t1.start();t2.start();latch.await();System.out.println((System.nanoTime() - start) /1_000_000 ms);}
}在自己电脑上(2.3 GHz 双核Intel Core i5)跑如上代码要接近3000豪秒了
如上程序是两个线程分别处理一个对象数组的不同变量而这个变量是个volatile long x; 因为数组的2个变量是在同一个缓存行中的每次修改都修改了同一个缓存行要有缓存同步操作所以比较慢。
再看如下程序只需修改T的定义
private static class T {public volatile long p1, p2, p3, p4, p5, p6, p7;public volatile long x;public volatile long p8, p9, p10, p11, p12, p13, p14;
}可以降低到1000毫秒左右因为数组的两个元素arr[0].x 与 arr[1].x 不会在一个缓存行中这样修改用的各自的缓存行互不影响
56字节
x(8字节)
56字节
56字节
x(8字节)
56字节当然使用Contended运行加上-XX:-RestrictContended是最方便的
CPU指令的乱序执行
cpu中为了能够让指令的执行尽可能地并行起来从而发明了流水线技术。但是如果两条指令的前后存在依赖关系比如数据依赖控制依赖等此时后一条语句就必需等到前一条指令完成后才能开始。cpu为了提高流水线的运行效率会做出比如
对无依赖的前后指令做适当的乱序和调度对控制依赖的指令做分支预测对读取内存等的耗时操作做提前预读…
这些都可能会导致指令乱序 附: 指令流水线是为提高处理器执行指令的效率把一条指令的操作分成多个细小的步骤取指、译码、执行、访问主存、写回每个步骤由专门的电路完成的方式。举个例子例如一条指令要执行要经过3个阶段取指令、译码、执行每个阶段都要花费一个机器周期如果没有采用流水线技术那么这条指令执行需要3个机器周期如果采用了指令流水线技术那么当这条指令完成取指后进入译码的同时下一条指令就可以进行取指了这样就提高了指令的执行效率。
通过代码实例认识到CPU指令乱序执行
google blog: Memory Reordering Caught in the Act
代码如下
public class Main {static int x, y, a, b;public static void main(String[] args) throws Exception{int i 0;while (true) {x 0;y 0;b 0;a 0;Thread A new Thread(new Runnable() {Overridepublic void run() {a 1;x b;}});Thread B new Thread(new Runnable() {Overridepublic void run() {b 1;y a;}});A.start();B.start();A.join();B.join();i;if(x 0 y 0){System.err.println(i x y);break;}}System.out.println(main end);}
}指令有序的话理论上不会出现xy都等于0的情况如果出现则可以说明指令乱序
如上程序运行一段时间后需要耐心等待一下退出输出如下 后续在认识线程安全可见性原子性顺序性的时候还将复习到此知识。