多线程之程序的局部性原理和伪共享问题
作者:互联网
程序的局部性原理
存储结构
在说局部性原理的时候,先来了解一下计算机的存储结构,设计到计算机组成结构的一些知识,计算机的存储结构,主要分以下几层,其中的磁盘跟本文无关。就暂且不提
- 内存:一般也叫主存,存储的是程序运行所需要的数据,操作指令,中间结果和最终结果等。cpu就是从内存中调取数据进运算器执行,然后控制器发出控制信号
- 高速缓存:也叫cache,位于cpu和内存之间,容量小,读写速度远远高于内存,接近cpu。一般和cpu集成在一起。主要为了解决内存和cpu内存不匹配的问题,cache从内存中取值是以缓存行为单位来取的。
- 寄存器:寄存器是cpu内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。
局部性原理
通过上面的简单介绍,可以知道,在内存和cpu之间有一道cache,cpu取值是先看cache中有没有,如果有就从cache中拿,没有再访问主存。并且cache读取数据是并不是一个一个数据来读的,而是将目标数据所在的内存区域的一个缓存行大小一起读,作为一个缓存行,然后复制存进缓存的。总结一下就是:缓存行是主存和cache数据交换的单位。为什么要这么设计呢。程序的局部性原理认为:
CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中
时间局部性:程序有在一段时间内多次访问同一个数据块的倾向
空间局部性:程序往往有访问一个聚集空间的数据块的倾向
通俗来说呢就是,你对一个数据进行访问的时候,cpu认为,这个数据附近的数据部分也有非常有可能被访问,于是缓存就会直接把这个数据附近的数据也一起放到缓存中,节省cpu再到内存找的性能开销。举个例子:数组。数组的存储位置是连续的,但是我们一般在编程的时候,很少会单独的去访问数组的单个元素,就像我们平时写代码一样,数组都是用for循环去遍历,如果不把数组的的元素连续区域放进cache中,那cpu每循环一次,都要去内存中取值,效率较低。但是如果根据局部性原理,把这个数组的这块某块区域的大小都放进cache中,那cpu每次遍历只需要直接从高速缓存中取值。效率就会大大的提高。例如像这样
long a,b,c,d;
这个时候当cpu访问变量a的时候,如果a在缓存中没有,并且缓存行的大小为32字节,那么这个时候,abcd都会作为一个缓存行存进缓存中。因为cpu认为,bcd变量在后续的指令执行中非常有可能用到。
局部性原理并不是定理,而是一种经验的规律的总结,是计算机科学家在研究程序的运行过程发现的规律的总结,因此提出的提高效率的一种解决办法,目前很多的cpu都采用了这种方式。
伪共享
在多线程的环境中,伪共享的问题就是由于上面所说的程序的局部性原理导致的。从上面知道,cpu和主存之间隔着一个缓存,而缓存存值又是以缓存行作为单位的,cpu对值的修改也是以缓存行为单位的。由于程序的局部性原理,cpu从缓存中读取数据都是按缓存行来读的,一个缓存行可能是32个字节,也可能是64个字节等等,不同的cpu设计会有不同的大小。假设有x变量和y变量是在同一行被cpu读到,但是此时同时有两个线程,线程1对x进行操作,线程2对y进行操作。虽然他们读取的变量都不一样,由于这两个变量是在同一行。那么如果此时线程1对x的变量值进行了修改,由于缓存一致性协议,就要通知线程2 x的值被改了需要重新到主存中读取,这样就不是真正的共享。而这样反复的读取主存,就会无意中影响彼此的性能, 这就是线程间的伪共享问题。
如何解决伪共享问题
解决伪共享问题有一个很好的思路就是缓存对齐或者说缓存填充,例如有这样的一段代码,假设缓存行大小为64个字节。
volatile long value = 0;
long p1,p2,p3,p4,p5,p6,p7;
long value2 = 2
只需要像这样,value和其他7个变量就会被放到同一个缓存行,value2又会被放到另一个缓存行中么,这样的话,对这个缓存行中的数据value和Value2读写就不会产生伪共享问题,因为他们是在不同的缓存行,这是一种用空间换时间的方式
在jdk1.8之后有一个注解,@sun.misc.centened.将这个注解放在变量上,会自动填充缓存行。但是这个注解要对jvm参数进行一些配置才会生效
标签:缓存,cache,线程,内存,共享,多线程,局部性,cpu 来源: https://www.cnblogs.com/blackmlik/p/12872458.html