存储器层次结构、Cache Line、伪共享、Cache Line对齐

存储器的层次结构:

  存储器是分层次的,离CPU越近的存储器,速度越快,每字节的成本越高,同时容量也越小。寄存器速度最快,离CPU最近,成本最高,所以个数容量有限,其次是高速缓存(缓存也是分级,有L1,L2,L3等缓存),再次是主存(普通内存),然后是本地磁盘,最次是远程文件存储。
在这里插入图片描述

存储器层次结构

缓存RegisterL1 cacheL2 cacheL3 cacheMain Memory硬盘
访问时间< 1ns约1ns约3ns约15ns约80ns约2ms
典型容量几十~几百B几十~几百KB几百KB几百KB~几MB几GB几百GB~几TB
Cache Line:

  高速缓存其实就是一组称之为cache line的固定大小的数据块,其大小是以突发读或者突发写周期的大小为基础的。即使处理器只存取一个字节的存储器,高速缓存控制器也启动整个存取器访问周期并请求整个数据块。缓存行第一个字节的地址总是突发周期尺寸的倍数。缓存行的起始位置总是与突发周期的开头保持一致。当从内存中取单元到cache中时,会一次取一个cache line大小的内存区域到cache中,然后存进相应的cache line中。

cache line

Cache Line示意图

  如图,两颗CPU(核)读取数据,如CPU1读X,CPU2读Y,Y与X处于同一个cache line,CPU1和CPU2会将X和Y所在的cache line全部读进来,ALU与Register在读取数据时,依次从L1、L2、L3、Main Memory中找数据,找到后按照相反的顺序依次缓存数据,该过程会有损失,但是使用缓存的损失更低。如果两颗CPU中间的数据需要保持一致性,如CPU1中X被修改,就必须告知CPU2中X已经过时,需要重新从内存中读取,CPU内部的数据同步过程以cache line为单位。这个协议称为缓存一致性协议,Intel采用的缓存一致性协议称为MESI Cache一致性协议。MESI 是指4中状态的首字母。每个Cache line有4个状态,可用2个bit表示,它们分别是:

状态描述监听任务
Modify(修改)该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。缓存行必须时刻监听所有试图读该缓存行相对就主存的操作,这种操作必须在缓存将该缓存行写回主存并将状态变成S(共享)状态之前被延迟执行。
Exclusive(独享、互斥)该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。缓存行也必须监听其它缓存读主存中该缓存行的操作,一旦有这种操作,该缓存行需要变成S(共享)状态。
Shared(共享)该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中。缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。
Invalid(无效)该Cache line无效。

  MESI Cache一致性协议是缓存锁的实现之一,还有MSI、MOSI、Synapse、Firefly、Dragon。有些无法被缓存的数据,或者跨越多个cache line的数据,依然必须使用总线锁,效率较低。下表示意了,当一个cache line调整状态的时候,另外一个cache line 需要调整的状态。

ModifyExclusiveSharedInvalid
ModifyXXX
ExclusveXXX
SharedXX
Invalid

  cache line越大,局部性空间效率越高,但读取时间慢;cache line越小,局部性空间效率越低,但读取时间快;取一个折中值,目前多用64字节。下列程序展示修改的两个数在不在同一个cache line情况下的时间消耗。

// 在同一cache line
public class SameCacheLine {
    public static volatile long[] array = new long[2];

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10_0000_0000L ; i++) {
                array[0] = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10_0000_0000L ; i++) {
                array[1] = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime()-start)/100_0000);
    }
}

// 输出:4366

----------------------------------------------------------------------------

// 不在同一cache line
public class DifferentCacheLine {
    public static volatile long[] array = new long[16];

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10_0000_0000L ; i++) {
                array[0] = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10_0000_0000L ; i++) {
                array[8] = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime()-start)/100_0000);
    }
}

// 输出:2276
cache line对齐:

  对于有些特别敏感的数字,会存在线程高竞争的访问,为了保证不发生伪共享,可以使用缓存航对齐的编程方式。在著名的框架Disruptor中就应用了cache line对齐,如下所示:

public long p1, p2, p3, p4, p5, p6, p7;  // cache line padding
private volatile long cursor = INITIAL_CURSOR_VALUE;
public long p8, p9, p10, p11, p12, p13, p14;  // cache line padding

  JDK7中,很多采用long padding提高效率;JDK8,加入了@Contended注解,被注解的类属性不与该类的其他属性处于同一cache line,需要加上:JVM -XX:-RestrictContended。因为前后加上7个long类型进行padding只对Inter的CPU有效,通过使用@Contended注解,虚拟机可以自动匹配CPU实现cache line对齐。

public class ContendedCacheLine {
    @Contended
    volatile long x;
    @Contended
    volatile long y;

    public static void main(String[] args) throws InterruptedException {
        ContendedCacheLine cacheLine = new ContendedCacheLine();
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10_0000_0000L ; i++) {
                cacheLine.x = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10_0000_0000L ; i++) {
                cacheLine.y = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime()-start)/100_0000);
    }
}

// 输出:
加上@Contended5399
不加@Contended: 28908
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值