伪共享

计算机分为CPU、内存、硬盘等部分,我们运行中的程序也就是进程运行在内存中(当内存不足时可能被交换到位于硬盘的swap区)。
进程中包括数据区、代码区,CPU将代码指令和数据通过总线获取到CPU中进行执行。
CPU中存在很多寄存器,CPU到寄存器的存取速度很快,但是寄存器的空间很小。内存的存取速度比寄存器慢,但是拥有更大的空间。
当内存访问速度远远落后于CPU时,将导致系统执行速度受到内存速度的瓶颈限制,因此为了缓冲两者间的速度差异,在直接增加了一些缓存。
缓存利用了局部性原理: CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。局部性又分为了时间局部性(一个数据在不久后还可能会被访问)、空间局部性(一个数据附近的数据在不久后可能会被访问到)、顺序局部性(大部分代码是顺序执行的)。这样在CPU和内存间增加缓存,来缓存刚使用到的数据,缓存的速度更快、相应的比内存空间小,能够提高系统的性能。当前一般的计算机架构从CPU到外层依次为CPU-> 寄存器 -> L1 Cache -> C2 Cache -> L3Cache -> 主内存 -> 磁盘。越靠近CPU的越小速度越快。CPU访问主内存的时间大概在80ns左右,而L1 Cache只需要约1ns。
cpu-arch

CPU依靠多核来提高执行能力后,每个核都有自己的一级、二级缓存,这样有带来了缓存一致性问题,一些CPU通过缓存一致性协议来同步缓存间的数据的一致性,应用程序可以通过一些内存屏障等机制进行数据同步。

需要注意的是缓存中不存储单个的项目,例如,不存储单个值、单个指针,缓存由缓存行(Cache Line)组成(通常是64bytes),并且和内存中的一个位置对应。Java中一个long值占用8bytes,所以一个缓存行可以放8个long变量。
所以当我们访问一个long数组时,获取一个值后,其相邻的值也被缓存到就近的缓存中,当我们迭代数组的时候就会很快速。

但是有一些情况可能比较糟糕,会造成缓存miss或频繁缓存失效。
假设有一个head引用,并且在其之后有另一个tail引用。现在把head load到缓存的时候,tail也会被缓存起来。但是现在问题是head是有消费者线程控制写入的,tail是由生产者控制写入的,它们两个可能不是一个线程,当更新head的时候,缓存值被更新、内存被更新,并且其他包含这个head的缓存行也要被失效来保证一致性。这样情况导致的缓存失效问题叫做伪共享(false sharing),因为每次访问head的时候,也会得到tail。
cache-invalidate-false-sharing

解决方式

防止其他数据导致缓存失效的问题常用增加padding,叫做缓存行填充的方式来解决,例如在前后加上无用的数据。

 

1

2

3

 

public long p1, p2, p3, p4, p5, p6, p7;

private volatile long cursor;

public long p8, p9, p10, p11, p12, p13, p14;

 

在32为平台上, 一个对象 有4bytes的MarkWord, 4bytes指向类,这样加起来128bytes就能够防止伪共享问题了。
在Disrutpor就是用了这一方式,

 

1

2

3

4

 

public final class RingBuffer<E> extends RingBufferFields<E> implements Cursored, EventSequencer<E>, EventSink<E>

{

public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE;

protected long p1, p2, p3, p4, p5, p6, p7;

JDK7的LinkedTransferQueue中也用到了padding

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

 

static final class PaddedAtomicReference<T> extends AtomicReference<T> {

Object p0;

Object p1;

Object p2;

Object p3;

Object p4;

Object p5;

Object p6;

Object p7;

Object p8;

Object p9;

Object pa;

Object pb;

Object pc;

Object pd;

Object pe;

PaddedAtomicReference(T r) {

super(r);

}

}

 

值得注意的是,这些优化细节比较底层,并不一定能够起作用,面对不同的编译器、运行时和CPU都有可能有不同的优化。

关键点:
局部性。缓存。缓存一致性。

参考

from: https://liuzhengyang.github.io/2017/04/13/false-sharing/ 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值