赞
踩
多核处理器把多个CPU(核心)集成到单个集成电路芯片(integrated circuit chip)中。一个双核的CPU有2个中央处理单元,所以2个不同的进程可以分别在不同的核心同时执行,大大加快了系统的速度。由于2个核心都在一个芯片上,因此它们之间的通信也要更快,系统也会有更小的延迟。
这是欺骗操作系统的行为,在物理上仍然只有1核,只不过在超线程CPU的角度上看,它认为它的超线程会加速程序的运行
多CPU之间共享内存
当使用一个程序,程序从硬盘中加载到内存当中,然后CPU将程序中的下一条指令地址读取到PC中,然后将相关数据存储到Registers(寄存器)中
但是存在一下问题: CPU的速度比内存快100倍
【CPU速度】CPU的速度非常快,CPU的速度大概是内存速度的100倍。从运算的单元到自己CPU内部存储器中去一个数据
【内存速度】内存也有速度,严格来说是从运算的单元访问到我们内存
现在有一条指令 内存读一条数据100s ,CPU寄存器进行运算单元计算需要1s
第一,先从内存中取一条数据
第二,通过CPU里面的寄存器对改数据进行 i++ 操作
这就导致了内存速度先执行完指令一才能继续执行CPU的速度的执令二,浪费100个等待时间,如何提升存储数据的速度呢?
因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存,这样会使CPU花费很长时间等待数据到来或把数据写入内存。
在在CPU内部建立位于CPU与内存之间的临时存储器(缓存
),它的容量比内存小的多但是交换速度却比内存要快得多。
这样CPU在执行的时候就可以先从高速缓存去找,如果找到之后就里面送到CPU去处理,如果没有找到,,就用相对慢的速度从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。
高速缓存考虑到CPU运行的数据大小与距离,操作系统把高速缓存拆分为3个缓存 L1 L2 L3 这样数据传输就类似与jvm的双亲委派。
Register | <1ns |
L1Cache | 约1ns |
L2Cache | 约3ns |
L3Cache | 约15ns |
main memory | 约80ns |
了解CPU的高速缓存,那么CPU高速缓存是怎么进行传输的呢?缓存行
左边是一整颗CPU,它有两个核,每个核它能运行一个线程 。每个核里面都有L1、L2,一颗CPU里面的多个核里面共享L3,多颗CPU去共享整个内存。
现在加入在内存中有一个数据X,CPU如何把他读到寄存器里面进行运算呢?
CPU首先会先从L1缓存中去找,找不到然后去L2缓存里面去找,如果在找不到就去L3缓存找,此时,还是找不到CPU直接从内存里面去读这个X,然后把他在三个缓存里面各复制一份,然后再下次执行的时候就可以快速找到。如果这个时候来了个Y变量,也需要读到CPU里面,通过许多次复制会导致效率不高,CPU把XY统一打包成缓存行一起读到高速缓存中
。(缓存行的大小受命中率与复制速度限制)
综合下来,目前缓存行的大小是64个字节。
并不是所有数据都会被缓存,比如一些较大的数据,缓存行无法容下,那么就只能每次都去主内存中读取
但是这样还好引起缓存伪共享的情况?
上述的缓存行图片 ,现在有两个线程,第一个线程再左边的CPU运行,第二个线程再右边的CPU运行,第一个线程用到的数据是X,第二个线程用到的数据是Y。
这样会产生一个问题:如果X,Y在同一个缓存行里面,第一个CPU只用X,第二个CPU只用Y,现在CPU都会把XY加入到缓存里面,现在如果X改了需要通知其他CPU整个缓存行被改过了,这个缓存行已经是Invalid状态,需要重新读一遍缓存行,现在第二个CPU已经改完后,它改动了Y,第一个CPU不需要去读Y,但是第一个CPU还需要去读一下缓存行,两个互相无关的值变来变去的时候,内部会产生缓存行互相影响问题 (效率低)。
伪共享:位于统一缓存行的两个不同的数据,被两个不同的CPU锁定,产生相互的影响伪共享问题
示例:现在有一个arr数组,里面存放这个两个T对象,对象里面值有一个Long类型,现在线程1频繁的在对象0中的X进行运算操作,线程2频繁的在对象1中的X进行操作。执行如下:
package com.tde.demo.缓存行; public class Cacheline_nopadding { public static class T{ //8字节 “java中long类型占用8个字节。 private volatile long x = 0L; } private static T[] arr = new T[2]; static { arr[0] = new T(); arr[1] = new T(); } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(()->{ for(long i = 0;i < 1000_0000L;i++){ //volatile的缓存一致性协议MESI或者锁总线,会消耗时间 arr[0].x = i; } }); Thread thread2 = new Thread(()->{ for(long i = 0;i< 1000_0000L;i++){ arr[1].x = i; } }); long startTime = System.nanoTime(); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("总计消耗时间:"+(System.nanoTime()-startTime)/100_000); } }
package com.tde.demo.缓存行; public class Cacheline_nopadding { private static class Padding{ //7*8字节 public volatile long p1,p2,p3,p4,p5,p6,p7; } public static class T extends Padding{ //8字节 private volatile long x = 0L; } // public static class T{ // //8字节 “java中long类型占用8个字节。 // private volatile long x = 0L; // } private static T[] arr = new T[2]; static { arr[0] = new T(); arr[1] = new T(); } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(()->{ for(long i = 0;i < 1000_0000L;i++){ //volatile的缓存一致性协议MESI或者锁总线,会消耗时间 arr[0].x = i; } }); Thread thread2 = new Thread(()->{ for(long i = 0;i< 1000_0000L;i++){ arr[1].x = i; } }); long startTime = System.nanoTime(); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("总计消耗时间:"+(System.nanoTime()-startTime)/100_000); } }
效率大大提高
。
基于高速缓存的存储交互很好的解决了CPU和内存的速度的矛盾,但也引入了一个新的问题 缓存一致性,在多处理器(CPU)系统中,每个CPU都有自己的高速缓存,而他们又共享同一主内存,当多个处理器运算任务都涉及到同一块主内存区域时,将可能导致各自的缓存数据不一致 了解决这个问题,需要各个处理器在访问内存时,需要遵循一些协议,例如MSI、EMSI、MOSI等
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。