一、CPU Cache

存储设备往往是速度越快价格越昂贵,速度越快价格越低廉。在计算机中,CPU 的速度远高于主存的速度,而主存的速度又远高于磁盘的速度。为了解决不同存储部件的速度不对等问题,让高速设备充分发挥性能,引入了多级缓存机制。

为了解决内存和 CPU 的速度不匹配问题,相继引入了 L1 Cache、L2 Cache、L3 Cache,数字越小,容量越小,速度越快,位置越接近 CPU。

现在的 CPU 都是由多个处理器,每个处理器由多个核心构成。一个处理器对应一个物理插槽,不同的处理器间通过 QPI 总线相连。一个处理器间的多核共享 L3 Cache。一个核包含寄存器、L1 Cache、L2 Cache,下图是Intel Sandy Bridge CPU架构:

二、缓存行与伪共享

缓存中的数据并不是独立的进行存储的,它的最小存储单位是缓存行,缓存行的大小是2的整数幂个字节,最常见的缓存行大小是 64 字节。CPU 为了执行的高效,会在读取某个对象时,从内存上加载 64 的整数倍的长度,来补齐缓存行。

以 Java 的 long 类型为例,它是 8 个字节,假设我们存在一个长度为 8 的 long 数组 arr,那么CPU 在读取 arr[0] 时,首先查询缓存,缓存没有命中,缓存就会去内存中加载。由于缓存的最小存储单位是缓存行,64 字节,且数组的内存地址是连续的,则将 arr[0] 到 arr[7] 加载到缓存中。后续 CPU 查询 arr[6] 时候也可以直接命中缓存。

现在假设多线程情况下,线程 A 的执行者 CPU Core-1 读取 arr[1],首先查询缓存,缓存没有命中,缓存就会去内存中加载。从内存中读取 arr[1] 起的连续的 64 个字节地址到缓存中,组成缓存行。由于从arr[1] 起,arr 的长度不足够 64 个字节,只够 56 个字节。假设最后 8 个字节内存地址上存储的是对象 bar,那么对象 bar 也会被一起加载到缓存行中。

现在有另一个线程 B,线程 B 的执行者 CPU Core-2 去读取对象 bar,首先查询缓存,发现命中了,因为 Core-1 在读取 arr 数组的时候也顺带着把 bar 加载到了缓存中。

这就是缓存行共享,听起来不错,但是一旦牵扯到了写入操作就不妙了。

假设 Core-1 想要更新 arr[7] 的值,根据 CPU 的 MESI 协议,那么它所属的缓存行就会被标记为失效。因为它需要告诉其他的 Core,这个 arr[7] 的值已经被更新了,缓存已经不再准确了,你必须得重新去内存拉取。但是由于缓存的最小单元是缓存行,因此只能把 arr[7] 所在的一整行给标识为失效。

此时 Core-2 就会很郁闷了,刚刚还能够从缓存中读取到对象 bar,现在再读取却被告知缓存行失效,必须得去内存重新拉取,延缓了 Core-2 的执行效率。

这就是缓存伪共享问题,两个毫无关联的线程执行,一个线程却因为另一个线程的操作,导致缓存失效。这两个线程其实就是对同一缓存行产生了竞争,降低了并发性。

三、Disruptor 缓存行填充

Disruptor 为了解决伪共享问题,使用的方法是缓存行填充。这是一种以空间换时间的策略,主要思想就是通过往对象中填充无意义的变量,来保证整个对象独占缓存行。

举个例子,以 Disruptor 中的 Sequence 为例,在 volatile long value 的前后各放置了 7 个 long 型变量,确保 value 独占一个缓存行。

public class Sequence extends RhsPadding {private static final long VALUE_OFFSET;static {VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));...}...
}class RhsPadding extends Value {protected long p9, p10, p11, p12, p13, p14, p15;
}class Value extends LhsPadding {protected volatile long value;
}class LhsPadding {protected long p1, p2, p3, p4, p5, p6, p7;
}

如下图所示,其中 V 就是 Value 类的 value,P 为 value 前后填充的无意义 long 型变量,U 为其它无关的变量。不论什么情况下,都能保证 V 不和其他无关的变量处于同一缓存行中,这样 V 就不会被其他无关的变量所影响。

这里的 V 也不限定为 long 类型,其实只要对象的大小大于等于8个字节,通过前后各填充 7 个 long 型变量,就一定能够保证独占缓存行。

此处以 Disruptor 的 RingBuffer 为例,最左边的 7 个 long 型变量被定义在顶级父类 RingBufferPad 中,最右边的 7 个 long 型变量被定义在 RingBuffer 的最后一行变量定义中,这样所有的需要独占的变量都被左右 long 型给包围,确保会独占缓存行。

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;...
}abstract class RingBufferFields<E> extends RingBufferPad
{...
}abstract class RingBufferPad {protected long p1, p2, p3, p4, p5, p6, p7;
}

四、@Contended

在 JDK 1.8 中,提供了 @sun.misc.Contended 注解,使用该注解就可以让变量独占缓存行,不再需要手动填充了。注意,JVM 需要添加参数 -XX:-RestrictContended 才能开启此功能。

如果该注解被定义在了类上,表示该类的每个变量都会独占缓存行;如果被定义在了变量上,通过指定 groupName,相同的 groupName 会独占同一缓存行。

// 类前加上代表整个类的每个变量都会在单独的cache line中
@sun.misc.Contended
public class ContendedData {int value;long modifyTime;boolean flag;long createTime;char key;
}// 同一 groupName 在同一缓存行
public class ContendedGroupData {@sun.misc.Contended("group1")int value;@sun.misc.Contended("group1")long modifyTime;@sun.misc.Contended("group2")boolean flag;@sun.misc.Contended("group3")long createTime;@sun.misc.Contended("group3")char key;
}

@Contended 在 JDK 源码中已经有所应用,以 Thread 类为例,为了保证多线程情况下随机数的操作不会产生伪共享,相关的变量被设置为同一 groupName。

public class Thread implements Runnable {...// The following three initially uninitialized fields are exclusively// managed by class java.util.concurrent.ThreadLocalRandom. These// fields are used to build the high-performance PRNGs in the// concurrent code, and we can not risk accidental false sharing.// Hence, the fields are isolated with @Contended./** The current seed for a ThreadLocalRandom */@sun.misc.Contended("tlr")long threadLocalRandomSeed;/** Probe hash value; nonzero if threadLocalRandomSeed initialized */@sun.misc.Contended("tlr")int threadLocalRandomProbe;/** Secondary seed isolated from public ThreadLocalRandom sequence */@sun.misc.Contended("tlr")int threadLocalRandomSecondarySeed;...
}

五、速度测试

将 volatile long value 封装为对象,四线程并行,每个线程循环 1 亿次,对 value 进行更新操作,测试缓存行对速度的影响。

CPU:AMD 3600 3.6 GHz,Memory:16 GB

作者: Jitwxs

链接: https://jitwxs.cn/13836b16.html

最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。

正文结束

推荐阅读 ↓↓↓

1.不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事

2.如何才能成为优秀的架构师?

3.从零开始搭建创业公司后台技术栈

4.程序员一般可以从什么平台接私活?

5.37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...

6.IntelliJ IDEA 2019.3 首个最新访问版本发布,新特性抢先看

7.漫画:程序员相亲图鉴,笑屎我了~

8.15张图看懂瞎忙和高效的区别!

一个人学习、工作很迷茫?

点击「阅读原文」加入我们的小圈子!

太牛逼了!自从项目中用了Disruptor之后,性能提升了2.5倍相关推荐

  1. 太牛逼了!项目中用了Disruptor之后,性能提升了2.5倍

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! ‍存储设备往往是速度越快价格越昂贵,速度越快价格越低廉.在 ...

  2. python做什么项目好_推荐两个牛逼的Python项目

    首先能问出这种问题的一定没好好看我之前写的这篇文章:丧心病狂的Github技巧,你要是学会了这篇文章的技巧,我相信你肯定不会再问我怎么找项目了. 但是看着有一两千的人给我的留言点赞,都想要Python ...

  3. python播放视频 命令_一行命令下载全网视频,这个命令太牛逼了!

    原标题:一行命令下载全网视频,这个命令太牛逼了! 相信大家有遇到这样的情况,在网站上看到一些不错的视频,想要下载下来,但是没有找到网站的下载入口. 虽然像优酷.爱奇艺.腾讯视频都有自己的客户端,但下载 ...

  4. 金庸说的:人不要太牛逼

    本文转自:http://blog.sina.com.cn/s/blog_bfc38ab40102xal0.html 一 人不能太牛逼,这是金庸告诉我们的道理. 今天就讲一个装牛逼被雷劈的小段落. 在& ...

  5. python 公众号文章发布_分享一个牛逼的Python项目:公众号文章爬虫

    我订阅了近 100 个公众号,有时候想再找之前读过的文章,发现搜索起来特别困难,如果忘了收藏,估计得找半小时,更让人无语的是,文章已经发布者删除,或者文章因违规被删除.那么有没有这样的爬虫,可以将公众 ...

  6. python中查看表头的函数_Python中也可以写Excel中的“Vlookup”函数?太牛逼了吧!...

    原标题:Python中也可以写Excel中的"Vlookup"函数?太牛逼了吧! Vlookup函数,可以算是一个数据专员必须要会使用的基本函数了,确实很好用.但是你可能会注意到, ...

  7. 太牛逼!一款软件几乎可以操作所有的数据库!

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 作者:不剪发的Tony老师 来源:http://suo.im/5OBiUi DBeav ...

  8. python项目2019_2019 年 11 月精选 GitHub 上 几个牛逼 Python 的项目

    熟悉小帅b的朋友们都知道,小帅b每个月都会精选几个比较不错的 GitHub 项目给大家参考参考,这不 2019 年只剩下最后一个月了,忍不住想问一句: 你被裁了么?你又胖了么? ok,咱们进入正题: ...

  9. python四大软件-太牛逼!一款软件几乎可以操作所有的数据库!

    作者: 不剪发的Tony老师 来源: http://suo.im/5OBiUi DBeaver是一个基于 Java 开发,免费开源的通用数据库管理和开发工具,使用非常友好的 ASL 协议.可以通过官方 ...

  10. 什么中文版软件可以操作mysql_一款软件,几乎可以操作~所有的~“数据库”,太牛逼了!...

    DBeaver是一个基于 Java 开发,免费开源的通用数据库管理和开发工具,使用非常友好的 ASL 协议.可以通过官方网站或者 Github 进行下载. 下载与安装 DBeaver 社区版可以通过官 ...

最新文章

  1. jca分析java dump日志
  2. 速计算机科学,计算机科学速成课30:万维网【视频】
  3. java的jps命令怎么使用_jps命令的使用方法
  4. 蚂蚁动态卡片,让App首页实现敏捷更新
  5. 启动hbase后hregionserver没有启动
  6. WordPress一个还不错的404html单页代码
  7. 配置中心、消息队列、分布式服务链路跟踪
  8. 2019公需科目快速学完_3周考过科目二,是这样做到的!
  9. thinkphp5项目--企业单车网站(二)
  10. 迪普应用防火墙产品线
  11. while循环基本使用方法
  12. XTU 设置教程 自动化 睡眠 休眠
  13. LimeSDR官方系列教程(六):使用Pothos和GNU Radio接收ASK/OOK信号
  14. iptables防火墙规则
  15. 请求的操作需要提升 windows7 route add命令 windows7添加路由
  16. 奶爸级教学---webpack详细教学
  17. String源码 spilt
  18. 300万数据导入导出优化方案,从80s优化到8s(实测)
  19. NVIDIA 第七届 SkyHackathon(一)比赛开发环境部署
  20. 点线形系列1-计算两点之间的距离

热门文章

  1. android app记录执行日志 捕获奔溃异常 ,存储日志到文件
  2. lucene5 排序
  3. JQuery动态增加删除元素
  4. socket 怎么设置心跳判断连接
  5. Linux 查看网络速率
  6. TG Pro for mac电脑温度管理工具
  7. 如何通过系统信息查看 Mac 上的显示刷新率?
  8. 如何在Mac上的Safari浏览器中输入画中画视频?
  9. MacOS 应对系统无响应的方法
  10. 蛋花花分享8个能提升Web前端开发技能的技巧