前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JDK之伪共享False Sharing 原

JDK之伪共享False Sharing 原

作者头像
克虏伯
发布2019-04-15 10:52:19
7540
发布2019-04-15 10:52:19
举报

    我了解伪分享是在看Disruptor源码时开始的。

1. @Contented注解

    JDK8中引入了@Contented,不过这个注解在sun包中,如下List-1

List-1

代码语言:javascript
复制
package sun.misc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Contended {
    String value() default "";
}

    这个注解只能在类属性、类上使用。

2. 我机器上CentOS的缓存行大小

    下面的Centos版本是Centos7 64位,运行在我的虚拟机上,可用的cpu核数是4,可用的运行内存是8192M,即8G。

    为了高效地存取缓存, 不是简单随意地将单条数据写入缓存的.  缓存是由缓存行组成的, 典型的一行是64字节,CentOS上可以查看到,如下:

List-2

代码语言:javascript
复制
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64

    来看下cache/index3到cache/index0的size变化,如下:

List-3 从index3到index0是逐渐变小(index0表示越接近CPU,index3表示离CPU越远)

代码语言:javascript
复制
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index3/size
8192K
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index2/size
256K
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index1/size
32K
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/size
32K

    来看下cache/index0到cache/index3的level变化,如下:

List-4 index3的level大于index0的

代码语言:javascript
复制
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/level
1
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index1/level
1
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index2/level
2
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index3/level
3

    来看下cache/index0到cache/index3的coherency_line_size值变化

List-5 值都是64

代码语言:javascript
复制
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index1/coherency_line_size
64
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index2/coherency_line_size
64
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index3/coherency_line_size
64

    来看下cache/index0到cache/index3的type

List-6 index0是Data、index1是Instruction、index2和index3都是Unified

代码语言:javascript
复制
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/type
Data
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index1/type
Instruction
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index2/type
Unified
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index3/type
Unified

      根据上面的数据,我猜测我的CentOS7上的CPU和缓存架构如下:

                                                   图1 猜测的我Centos上的CPU、缓存结构图

    注:图1仅仅是我猜测,很可能是错误的,建议读者自己去查看自己Linux系统中上述的值。

    mac系统查看"L1 data cache line size"的命令如下,参考自google论坛:

List-7 我mac系统上,得到的值也是64

代码语言:javascript
复制
mjduan@mjduandeMacBook-Pro:/tmp % sysctl hw.cachelinesize
hw.cachelinesize: 64

    CentOS上查看"L1 data cache line size"的命令如下,参考自google论坛:

List-8 我CentOS上,得到的值也是64

代码语言:javascript
复制
[dmj@localhost ~]$ getconf LEVEL1_DCACHE_LINESIZE
64

    上面做的基本是铺垫,由我们自己验证得缓存行是64bytes,怎么确定单位是bytes,参考国外博客,之后我们进一步进入伪共享。我发现这篇51CTO博客讲的挺好的,这里就不再重复描述。关键点在于:假设俩个独立的变量x、y内存上位于同一个缓存行中,CPU0要对x进行操作,CPU1要对y进行操作。当CPU0加载换成到自己的L0缓存中,CPU1加载缓存行到自己的L0缓存中,之后CPU0修改自己L0缓存中的x,此时CPU1的L0缓存中的缓存行是失效了,所以此时CPU1不能对自己缓存行进行操作,CPU1不得不重新从L3或者主存加载该缓存行。所以CPU0和CPU1得不到并行执行。

    经过上述讨论,引出一个问题,CPU1的L0中缓存行怎么知道,CPU0该缓存行中的某些数据,这个要了解下MESI协议,可以了解下这篇博客

3.怎么避免伪分享

3.1 填充(不是很建议使用填充,用@Contented,可以参考这个)

    "对于HotSpot JVM,所有对象都有两个字长的对象头。第一个字是由24位哈希码和8位标志位(如锁的状态或作为锁对象)组成的Mark Word。第二个字是对象所属类的引用。如果是数组对象还需要一个额外的字来存储数组的长度。每个对象的起始地址都对齐于8字节以提高性能",这段话来自于51CTO开发频道博客。这让我想起来《深入理解计算机》这本书中讲过变量声明顺序对内存的优化。

    通常下给出的是填充缓存行,如下List-9。

List-9 "public long p1, p2, p3, p4, p5, p6; // comment out"这行就是填充使用的

代码语言:javascript
复制
public final static class VolatileLong { 
        public volatile long value = 0L; 
        public long p1, p2, p3, p4, p5, p6; // comment out 
}

    但是这个要求开发人员对计算机底层和JVM内存有深的了解,要优化,但是也要慎重:

  •     没有达到优化的效果,反过来让程序代码可读性、可维护性变差。
  •     Java编译器会不会在编译时对代码进行优化?比如编译器在编译期间将"public long p1, p2, p3, p4, p5, p6; "移除导致伪分享又发生,因为这几个变量没有被使用到。这个我想可以通过Javap命令来查看,不了解Javap命令的同学可以去google/bing.com搜索下,这是java自带的命令。
  •     JVM的JustInTime,即JIT,在Java代码执行时会不会对代码进行优化?它在执行期间检测到"public long p1, p2, p3, p4, p5, p6; "这个变量没有被使用到,所以将这些移除,使得伪分享又发生。Stackoverflow上有人使用openJDK的jol分析了,结果说"JIT can't figure that some fields are unused and doesn't optimize them out."。不过这个我没有自己试验过,后面有时间再去验证。

    个人主观的认为,没有被使用的局部变量很有可能被优化,但是没有被使用到的类属性被优化的可能性不是很大,注意,这个仅仅是主观的认为,没有试验验证过。

3.2 JDK的@Contented注解

    JDK中的@Contented注解,这个的作用我没有仔细验证过,且这个注解是在sun.misc包中的,这个在开头给出了。Oracle的博客给出说这个注解可以一定程度上减少False sharing的发生。

    注意经过本人验证,JDK8上加上@Contented注解是不会生效的,除非加上List-10中的JVM参数。我是怎么知道不加List-10的参数,@Contented不会生效的呢,请参考这里,链接里面的是个OpenJDK jol的例子,在运行是测试下加上和不加上List-10JVM参数的结果,运行结果可以看到占用内存bits位数的变化。

List-10

代码语言:javascript
复制
-XX:-RestrictContended

    除了填充和@Contented外,很有可能有其它方法,建议读者多google/bing.com,也许能找到更充分的证据,最后是要自己验证。

    最好是自己测试下,如果测试发现优化后确实效率高了,可以运行在生产上,特别是注意测试数据量大的情况和长时间运行的情况。 

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018/06/27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. @Contented注解
  • 2. 我机器上CentOS的缓存行大小
  • 3.怎么避免伪分享
    • 3.1 填充(不是很建议使用填充,用@Contented,可以参考这个)
      • 3.2 JDK的@Contented注解
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档