太牛逼了!自从项目中用了Disruptor之后,性能提升了2.5倍
一、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倍相关推荐
- 太牛逼了!项目中用了Disruptor之后,性能提升了2.5倍
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 存储设备往往是速度越快价格越昂贵,速度越快价格越低廉.在 ...
- python做什么项目好_推荐两个牛逼的Python项目
首先能问出这种问题的一定没好好看我之前写的这篇文章:丧心病狂的Github技巧,你要是学会了这篇文章的技巧,我相信你肯定不会再问我怎么找项目了. 但是看着有一两千的人给我的留言点赞,都想要Python ...
- python播放视频 命令_一行命令下载全网视频,这个命令太牛逼了!
原标题:一行命令下载全网视频,这个命令太牛逼了! 相信大家有遇到这样的情况,在网站上看到一些不错的视频,想要下载下来,但是没有找到网站的下载入口. 虽然像优酷.爱奇艺.腾讯视频都有自己的客户端,但下载 ...
- 金庸说的:人不要太牛逼
本文转自:http://blog.sina.com.cn/s/blog_bfc38ab40102xal0.html 一 人不能太牛逼,这是金庸告诉我们的道理. 今天就讲一个装牛逼被雷劈的小段落. 在& ...
- python 公众号文章发布_分享一个牛逼的Python项目:公众号文章爬虫
我订阅了近 100 个公众号,有时候想再找之前读过的文章,发现搜索起来特别困难,如果忘了收藏,估计得找半小时,更让人无语的是,文章已经发布者删除,或者文章因违规被删除.那么有没有这样的爬虫,可以将公众 ...
- python中查看表头的函数_Python中也可以写Excel中的“Vlookup”函数?太牛逼了吧!...
原标题:Python中也可以写Excel中的"Vlookup"函数?太牛逼了吧! Vlookup函数,可以算是一个数据专员必须要会使用的基本函数了,确实很好用.但是你可能会注意到, ...
- 太牛逼!一款软件几乎可以操作所有的数据库!
点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 作者:不剪发的Tony老师 来源:http://suo.im/5OBiUi DBeav ...
- python项目2019_2019 年 11 月精选 GitHub 上 几个牛逼 Python 的项目
熟悉小帅b的朋友们都知道,小帅b每个月都会精选几个比较不错的 GitHub 项目给大家参考参考,这不 2019 年只剩下最后一个月了,忍不住想问一句: 你被裁了么?你又胖了么? ok,咱们进入正题: ...
- python四大软件-太牛逼!一款软件几乎可以操作所有的数据库!
作者: 不剪发的Tony老师 来源: http://suo.im/5OBiUi DBeaver是一个基于 Java 开发,免费开源的通用数据库管理和开发工具,使用非常友好的 ASL 协议.可以通过官方 ...
- 什么中文版软件可以操作mysql_一款软件,几乎可以操作~所有的~“数据库”,太牛逼了!...
DBeaver是一个基于 Java 开发,免费开源的通用数据库管理和开发工具,使用非常友好的 ASL 协议.可以通过官方网站或者 Github 进行下载. 下载与安装 DBeaver 社区版可以通过官 ...
最新文章
- jca分析java dump日志
- 速计算机科学,计算机科学速成课30:万维网【视频】
- java的jps命令怎么使用_jps命令的使用方法
- 蚂蚁动态卡片,让App首页实现敏捷更新
- 启动hbase后hregionserver没有启动
- WordPress一个还不错的404html单页代码
- 配置中心、消息队列、分布式服务链路跟踪
- 2019公需科目快速学完_3周考过科目二,是这样做到的!
- thinkphp5项目--企业单车网站(二)
- 迪普应用防火墙产品线
- while循环基本使用方法
- XTU 设置教程 自动化 睡眠 休眠
- LimeSDR官方系列教程(六):使用Pothos和GNU Radio接收ASK/OOK信号
- iptables防火墙规则
- 请求的操作需要提升 windows7 route add命令 windows7添加路由
- 奶爸级教学---webpack详细教学
- String源码 spilt
- 300万数据导入导出优化方案,从80s优化到8s(实测)
- NVIDIA 第七届 SkyHackathon(一)比赛开发环境部署
- 点线形系列1-计算两点之间的距离