目录

缓存行与伪共享

Disruptor 缓存行填充

@Contended

速度测试


存储设备往往是速度越快价格越昂贵,速度越快价格越低廉。在计算机中,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 就不会被其他无关的变量所影响。

Padding 填充

这里的 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

@Contended / Disruptor 缓存行占满注解相关推荐

  1. 不固定图片宽高瀑布流_图片横向等高瀑布流,每行占满,限制行数 的实现

    图片的横向瀑布流,其实简单地按顺序排列就可以了 但要实现每行中各图片都等高(各行不一定等高,但每行里面等高),且每行都占满,就需要用到flex的特性了 控制每行图片高度都一致,可能会影响图片的比例,所 ...

  2. Java8中@Contended和伪共享 进行缓存行填充

    Java8引入了@Contented这个新的注解来减少伪共享(False Sharing)的发生.本文介绍了@Contented注解并解释了为什么False Sharing是如何影响性能的. 缓存行 ...

  3. 从缓存行出发理解volatile变量、伪共享False sharing、disruptor

    volatile关键字 当变量被某个线程A修改值之后,其它线程比如B若读取此变量的话,立刻可以看到原来线程A修改后的值 注:普通变量与volatile变量的区别是volatile的特殊规则保证了新值能 ...

  4. 从CPU缓存行说说JDK8的@Contended

    从jdk8的@Contended说CPU缓存行: CPU从内存中读取数据实际上是按块读取的,而这个块的大小设定将很大程度的影响着程序执行效率: CPU在处理完一个数据后,会去处理接下来的数据,所以将内 ...

  5. Java8的伪共享和缓存行填充--@Contended注释

    在我的前一篇文章<伪共享和缓存行填充,从Java 6, Java 7 到Java 8>中, 我们演示了在Java 8中,可以采用@Contended在类级别上的注释,来进行缓存行填充.这样 ...

  6. java disruptor压测_Java并发框架Disruptor实现原理与源码分析(二) 缓存行填充与CAS操作...

    ##缓存行填充 关于缓存行填充在我个人的印象里面第一次看到是在Java的java.util.concurrent包中,因为当时很好奇其用法背后的逻辑,所以查了很多资料才明白到底是怎么回事*(也许事实上 ...

  7. 面试准备每日系列:计算机底层之并发编程(二)缓存行、一致性协议、伪共享、disruptor、CAS等待

    文章目录 1. 缓存行 Cache line 2. 缓存一致性协议 & 伪共享 3. 为什么不加volatile? 4. 编程先可用再调优 5. disruptor & CAS等待 1 ...

  8. CPU/内存/缓存行/Disruptor

    CPU/内存/缓存行/Disruptor:缓存分为多级缓存(L1/L2/L3...多级缓存)+主存mainMemory,内存中存储的是运行程序和所需数据, 不同级别的缓存大小分配不一样,而且反应速度不 ...

  9. 一篇对伪共享、缓存行填充和CPU缓存讲的很透彻的文章

    认识CPU Cache CPU Cache概述 随着CPU的频率不断提升,而内存的访问速度却没有质的突破,为了弥补访问内存的速度慢,充分发挥CPU的计算资源,提高CPU整体吞吐量,在CPU与内存之间引 ...

最新文章

  1. MySQL面试题 | 附答案解析(十四)
  2. C语言如何实现随机打印24个母,菜鸟求助,写一个随机输出26个英文字母的程序...
  3. python实现简单的api接口-使用Python编写API接口和使用API接口
  4. 【若依(ruoyi)】Swagger 上传接口
  5. 手把手教你-如何查询中文期刊是否属于核心期刊!
  6. 分享一个基于jQuery,backbone.js和underscore.js的消息提示框架 - Backbone.Notifier
  7. 服务器热修复,热修复探究,hotfix,patch
  8. 没有bug队——加贝——Python 练习实例 17,18
  9. MySql | 为什么大家都在说 Select * 效率低
  10. vue 数组转集合_思想实验:如何在Vue中使localStorage具有响应式?
  11. LLVM每日谈之一 LLVM是什么
  12. 如何关闭远程桌面后仍处于可交互状态
  13. 基于Springboot的高校课程管理系统 课程设计报告 毕业设计 包括报告和程序包
  14. DOS各版本下载地址
  15. Opencv中convertTo函数
  16. matlab陷波带阻滤波器,matlab陷波滤波器 陷波滤波器器应放在系统的闭环吗
  17. 致远OA自定义函数--正则表达式匹配校验
  18. 权重推送 产品定位 直通车投放 关键词 直通车人群 创意标题 补单 新手上路,直通车烧钱没效果怎么办?
  19. 找不到 Microsoft Excel Driver ODBC 驱动程序的安装例程 解决方案
  20. 闭合导线的近似平均差(工程测量)

热门文章

  1. 单片机入门——动态数码管显示
  2. 什么是哈希?什么是哈希函数?什么是哈希树?
  3. 转:当你拼命的时候别人会有多少在拼命
  4. python 实现 Pixiv 爬虫:下载画师的所有插画
  5. Java-JDK版本
  6. 海南渔民称在南沙南部常遭马来西亚军舰骚扰
  7. matlab结构体如何引用,matlab结构体数组引用
  8. 【Java】Mac上System.loadLibrary(xxx)出现no xxx in java.library.path
  9. jQuery:设置获取属性、遍历添加删除元素、尺寸、位置
  10. BCJC43:发展到遍地开花的实验室将何去何从