什么是伪共享?Java8如何使用@sun.misc.Contended避免伪共享?

news/2024/5/17 18:46:56/文章来源:https://blog.csdn.net/qq_37284798/article/details/126641566

什么是伪共享

缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

缓存行上的写竞争是运行在SMP系统中并行线程实现可伸缩性最重要的限制因素。有人将伪共享描述成无声的性能杀手,因为从代码中很难看清楚是否会出现伪共享。

为了让可伸缩性与线程数呈线性关系,就必须确保不会有两个线程往同一个变量或缓存行中写。两个线程写同一个变量可以在代码中发现

下面的图说明了伪共享的问题:

 

假设在核心1上运行的线程想更新变量X,同时核心2上的线程想要更新变量Y。不幸的是,这两个变量在同一个缓存行中。每个线程都要去竞争缓存行的所有权来更新变量。如果核心1获得了所有权,缓存子系统将会使核心2中对应的缓存行失效。当核心2获得了所有权然后执行更新操作,核心1就要使自己对应的缓存行失效。这会来来回回的经过L3缓存,大大影响了性能。如果互相竞争的核心位于不同的插槽,就要额外横跨插槽连接,问题可能更加严重。

避免伪共享

假设有一个类中,只有一个long类型的变量:

public final static class VolatileLong {public volatile long value = 0L;
}

这时定义一个VolatileLong类型的数组,然后让多个线程同时并发访问这个数组,这时可以想到,在多个线程同时处理数据时,数组中的多个VolatileLong对象可能存在同一个缓存行中,通过上文可知,这种情况就是伪共享。

怎么样避免呢?在Java 7之前,可以在属性的前后进行padding,例如:

public final static class VolatileLong {volatile long p0, p1, p2, p3, p4, p5, p6;public volatile long value = 0;volatile long q0, q1, q2, q3, q4, q5, q6;
}

通过Java对象内存布局文章中结尾对paddign的分析可知,由于都是long类型的变量,这里就是按照声明的顺序分配内存,那么这可以保证在同一个缓存行中只有一个VolatileLong对象。

__ 这里有一个问题:据说Java7优化了无用字段,会使这种形式的补位无效,但经过测试,无论是在JDK 1.7 还是 JDK 1.8中,这种形式都是有效的。网上有关伪共享的文章基本都是来自Martin的两篇博客,这种优化方式也是在他的博客中提到的。但国内的文章貌似根本就没有验证过而直接引用了此观点,这也确实迷惑了一大批同学!__

在Java 8中,提供了@sun.misc.Contended注解来避免伪共享,原理是在使用此注解的对象或字段的前后各增加128字节大小的padding,使用2倍于大多数硬件缓存行的大小来避免相邻扇区预取导致的伪共享冲突。具体可以参考http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html。

下面用代码来看一下加padding和不加的效果:

运行环境:JDK 1.8,macOS 10.12.4,2.2 GHz Intel Core i7,四核-八线程

public class FalseSharing implements Runnable {public final static int NUM_THREADS = 4; // changepublic final static long ITERATIONS = 500L * 1000L * 1000L;private final int arrayIndex;private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
//    private static VolatileLong2[] longs = new VolatileLong2[NUM_THREADS];
//    private static VolatileLong3[] longs = new VolatileLong3[NUM_THREADS];static {for (int i = 0; i < longs.length; i++) {longs[i] = new VolatileLong();}}public FalseSharing(final int arrayIndex) {this.arrayIndex = arrayIndex;}public static void main(final String[] args) throws Exception {long start = System.nanoTime();runTest();System.out.println("duration = " + (System.nanoTime() - start));}private static void runTest() throws InterruptedException {Thread[] threads = new Thread[NUM_THREADS];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(new FalseSharing(i));}for (Thread t : threads) {t.start();}for (Thread t : threads) {t.join();}}public void run() {long i = ITERATIONS + 1;while (0 != --i) {longs[arrayIndex].value = i;}}public final static class VolatileLong {public volatile long value = 0L;}// long padding避免false sharing// 按理说jdk7以后long padding应该被优化掉了,但是从测试结果看padding仍然起作用public final static class VolatileLong2 {volatile long p0, p1, p2, p3, p4, p5, p6;public volatile long value = 0L;volatile long q0, q1, q2, q3, q4, q5, q6;}/*** jdk8新特性,Contended注解避免false sharing* Restricted on user classpath* Unlock: -XX:-RestrictContended*/@sun.misc.Contendedpublic final static class VolatileLong3 {public volatile long value = 0L;}
}

VolatileLong对象只有一个long类型的字段,VolatileLong2加了padding,下面分别执行看下时间:

duration = 57293259577
duration = 4679059000

没加padding时用了大概57秒,加padding后用时大概4.6秒,可见加padding后有效果了。

在Java8中提供了@sun.misc.Contended来避免伪共享,例如这里的VolatileLong3,在运行时需要设置JVM启动参数-XX:-RestrictContended,运行一下结果如下:

duration = 4756952426

结果与加padding的时间差不多。

下面看一下VolatileLong对象在运行时的内存大小:

再来看下VolatileLong2对象在运行时的内存大小:

因为多了14个long类型的变量,所以24+8*14=136字节。

下面再来看下使用@sun.misc.Contended注解后的对象内存大小:

 

在堆内存中并没有看到对变量进行padding,大小与VolatileLong对象是一样的。

这就奇怪了,看起来与VolatileLong没什么不一样,但看一下内存的地址,用十六进制算一下,两个VolatileLong对象地址相差24字节,而两个VolatileLong3对象地址相差280字节。这就是前面提到的@sun.misc.Contended注解会在对象或字段的前后各增加128字节大小的padding,那么padding的大小就是256字节,再加上对象的大小24字节,结果就是280字节,所以确实是增加padding了。

八线程运行比四线程运行还快?

根据上面的代码,把NUM_THREADS改为8,测试看下结果:

VolatileLong:  44305002641
VolatileLong2: 7100172492
VolatileLong3: 7335024041

可以看到,加了padding和@sun.misc.Contended注解的运行时间多了不到1倍,而VolatileLong运行的时间比线程数是4的时候还要短,这是为什么呢?

再说一下,我的CPU是四核八线程,每个核有一个L1 Cache,那么我的环境一共有4个L1 Cache,所以,2个CPU线程会共享同一个L1 Cache;由于VolatileLong对象占用24字节内存,而代码中VolatileLong对象是保存在数组中的,所以内存是连续的,2个VolatileLong对象的大小是48字节,这样一来,对于缓存行大小是64字节来说,每个缓存行只能存放2个VolatileLong对象。

通过上面的分析可知,伪共享发生在L3 Cache,如果每个核操作的数据不在同一个缓存行中,那么就会避免伪共享的发生,所以,8个线程的情况下其实是CPU线程共享了L1 Cache,所以执行的时间可能比4线程的情况还要短。下面看下执行时4线程和8线程的CPU使用情况:

 

可以看到,在4线程时,线程被平均分配到了4个核中,这样一来,L1 Cache肯定是不能共享的,这时会发生伪共享;而8线程时,每个核都使用了2个线程,这时L1 Cache是可以共享的,这在一定程度上能减少伪共享的发生,从而时间会变短(也不一定,但总体来说8线程的情况与4线程的运行时间几乎不会向加padding和注解的方式差那么多)。

在Windows上情况就不太一样了,在双核四线程的CPU上,测试结果并不和mac中一样,在不加padding和注解时,2线程和4线程执行的时间都是将近差了1倍,看下使用2个线程在Windows中执行的时候CPU的使用情况:

 

虽然只使用了2个线程,但从图像上来看,似乎都在工作,即使把线程数量设置为1也是这种情况。这应该是Windows和UNIX对CPU线程调度的方式不一样,具体我现在也不太清楚他们之间的差别,希望有知道的同学告知,感谢。

@sun.misc.Contended注解

上文中将@sun.misc.Contended注解用在了对象上,@sun.misc.Contended注解还可以指定某个字段,并且可以为字段进行分组,下面通过代码来看下:

/*** VM Options: * -javaagent:/Users/sangjian/dev/source-files/classmexer-0_03/classmexer.jar* -XX:-RestrictContended*/
public class ContendedTest {byte a;@sun.misc.Contended("a")long b;@sun.misc.Contended("a")long c;int d;private static Unsafe UNSAFE;static {try {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);UNSAFE = (Unsafe) f.get(null);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}public static void main(String[] args) throws NoSuchFieldException {System.out.println("offset-a: " + UNSAFE.objectFieldOffset(ContendedTest.class.getDeclaredField("a")));System.out.println("offset-b: " + UNSAFE.objectFieldOffset(ContendedTest.class.getDeclaredField("b")));System.out.println("offset-c: " + UNSAFE.objectFieldOffset(ContendedTest.class.getDeclaredField("c")));System.out.println("offset-d: " + UNSAFE.objectFieldOffset(ContendedTest.class.getDeclaredField("d")));ContendedTest contendedTest = new ContendedTest();// 打印对象的shallow sizeSystem.out.println("Shallow Size: " + MemoryUtil.memoryUsageOf(contendedTest) + " bytes");// 打印对象的 retained sizeSystem.out.println("Retained Size: " + MemoryUtil.deepMemoryUsageOf(contendedTest) + " bytes");}}

这里还是使用到了classmexer.jar,可以参考Java对象内存布局中的说明。

这里在变量b和c中使用了@sun.misc.Contended注解,并将这两个变量分为1组,执行结果如下:

offset-a: 16
offset-b: 152
offset-c: 160
offset-d: 12
Shallow Size: 296 bytes
Retained Size: 296 bytes

可见int类型的变量的偏移地址是12,也就是在对象头后面,因为它正好是4个字节,然后是变量a。@sun.misc.Contended注解的变量会加到对象的最后面,这里就是b和c了,那么b的偏移地址是152,之前说过@sun.misc.Contended注解会在变量前后各加128字节,而byte类型的变量a分配完内存后这时起始地址应该是从17开始,因为byte类型占1字节,那么应该补齐到24,所以b的起始地址是24+128=152,而c的前面并不用加128字节,因为b和c被分为了同一组。

我们算一下c分配完内存后,这时的地址应该到了168,然后再加128字节,最后大小就是296。内存结构如下:

| d:12~16 | --- | a:16~17 | --- | 17~24 | --- | 24~152 | --- | b:152~160 | --- | c:160~168 | --- | 168~296 |

现在把b和c分配到不同的组中,代码做如下修改:

/*** VM Options:* -javaagent:/Users/sangjian/dev/source-files/classmexer-0_03/classmexer.jar* -XX:-RestrictContended*/
public class ContendedTest {byte a;@sun.misc.Contended("a")long b;@sun.misc.Contended("b")long c;int d;private static Unsafe UNSAFE;static {try {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);UNSAFE = (Unsafe) f.get(null);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}public static void main(String[] args) throws NoSuchFieldException {System.out.println("offset-a: " + UNSAFE.objectFieldOffset(ContendedTest.class.getDeclaredField("a")));System.out.println("offset-b: " + UNSAFE.objectFieldOffset(ContendedTest.class.getDeclaredField("b")));System.out.println("offset-c: " + UNSAFE.objectFieldOffset(ContendedTest.class.getDeclaredField("c")));System.out.println("offset-d: " + UNSAFE.objectFieldOffset(ContendedTest.class.getDeclaredField("d")));ContendedTest contendedTest = new ContendedTest();// 打印对象的shallow sizeSystem.out.println("Shallow Size: " + MemoryUtil.memoryUsageOf(contendedTest) + " bytes");// 打印对象的 retained sizeSystem.out.println("Retained Size: " + MemoryUtil.deepMemoryUsageOf(contendedTest) + " bytes");}}

运行结果如下:

offset-a: 16
offset-b: 152
offset-c: 288
offset-d: 12
Shallow Size: 424 bytes
Retained Size: 424 bytes

可以看到,这时b和c中增加了128字节的padding,结构也就变成了:

| d:12~16 | --- | a:16~17 | --- | 17~24 | --- | 24~152 | --- | b:152~160 | --- | 160~288 | --- | c:288~296 | --- | 296~424 |

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_381791.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网课搜题公众号接口 大学生新手使用必备

网课搜题公众号接口 大学生新手使用必备 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后…

风控模型黑箱可解释,试下这个方法来演示

模型的开发&#xff0c;目前在互金领域场景中因为变量多&#xff0c;开发周期短&#xff0c;目前用得最多的就是XGB、LGB这类的机器学习模型。 比如我们之前跟大家输出的关于个人信贷反欺诈评分卡的开发内容里&#xff0c;我们用的就是lightgbm来建模的&#xff0c;相关的操作细…

设计模式--简单工厂方法

简介 简单工厂模式属于创建型模式,是工厂模式的一种。简单工厂模式通过定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类,这个父类具有共有的属性和方法。因在简单工厂模式中用于创建实例的方法通常是静态方法,因此也称为静态工厂方…

SpringBoot整合Flowable工作流引擎框架

Flowable工作流引擎框架介绍 一个Java编写的轻量级业务流程引擎&#xff0c;为开发人员、系统管理员和业务用户提供工作流和业务流程管理&#xff08;BPM&#xff09;平台。不仅包括BPMN&#xff0c;还有DMN决策表和CMMN Case管理引擎&#xff0c;并且有自己的用户管理、微服务…

新机器(禁止上网)安装vscode及公钥方式登陆linux

1.1 新机器(禁止上网)安装vscode 注意:以下三个程序版本必须一至。 1) vscodeWin10安装程序 2) win10插件(ssh客户端) 3) linux里vscode-server-linux-x64.tar.gz(ssh服务端)方法一:从原桌面直接copy文件夹(绿色)转移到新机器 方法二:安装新的VSCodeUserSetup-x64-1.70.2.ex…

macOS分发app打包+签名+公证+添加票据+生成dmg文件

1.打包 网上有很多使用命令行的打包的方式大家可自行查找,以下是使用Xcode进行打包. 首先配置证书要配置Developer ID Application证书然后使用的是Xcode进行打包:Product->Archive 这种打包方式的好处是省去了签名的过程,但是用网上其他人的命令查看签名时候还是未成功&a…

lararvel学习文档

学习文档 http://laravel.p2hp.com/ http://laravel.p2hp.com/cndocs/9.x/eloquent#generating-model-classes laravel9.x https://learnku.com/docs/laravel/9.x laravel8.5 https://learnku.com/docs/laravel/8.5 laravel8.x https://learnku.com/docs/laravel/8.x …

CentOS7安装MySQL(完整版)

在CentOS中默认安装有MariaDB&#xff0c;这个是MySQL的分支&#xff0c;但为了需要&#xff0c;还是要在系统中安装MySQL&#xff0c;而且安装完成之后可以直接覆盖掉MariaDB。 1 下载并安装MySQL官方的 Yum Repository [rootlocalhost ~]# wget -i -c http://dev.mysql.com…

最新小红书数据 小红书爬虫 小红书接口 xhs 小红书算法 小红书api

最新版小红书APP接口,需要交流的朋友联系,少量勿扰,谢谢! 只取APP公开数据,不做违法事情,如有侵犯贵公司,请联系删除! 博主详情 笔记详情 博主笔记列表 笔记评论 关键词搜索等等接口已部署,支持并发,可测试!

openmmlab 教程1-安装

文章目录openmmlab 教程11. 安装1.1 介绍1.2 安装1) 安装mmcv-full(建议)2) 安装mmseg3) mmcv-full和mmcv区别1.3 验证安装成功1) 源码安装2) pip 安装openmmlab 教程1 官方文档 安装 MMCV — mmcv 1.6.1 文档 依赖 — MMSegmentation 0.27.0 文档 笔记链接 https://gitee.co…

windows安装JDK与系统变量配置

目录 问题现象&#xff1a; 解决方法&#xff1a; 1、安装 2、系统变量配置 2.1、JAVA_HOME 2.2、Path 2.3、CLASSPATH 问题现象&#xff1a; 今天梳理了一下JDK的安装和系统变量配置&#xff01; 解决方法&#xff1a; 1、安装 双击下载好的jdk安装包&#xff1a; 安装…

【区块链 | 默克尔树】如何利用Merkle实现空投,像Uniswap一样使用Merkle执行Airdrop

如果你想直接跳过如何实现 Uniswap Airdrop,请继续阅读以下部分:创建 Merkle Airdrop 的步骤 图片来自 https://ccoingossip.com/what-is-airdrop-in-crypto-world/ Airdrop Airdrop 是指项目决定向一组用户分发代币的事件。以下是实现 Airdrop 的一些潜在方法: 1. 管理员…

2.1 Elasticsearch DSL搜索-数据准备

自定义词库建立索引dsl_search(名字随意)手动建立mappingsPOST /dsl_search/_mapping { "properties": { "id": { "type": "long" }, "age": { "type&quo…

High Performance Computing 综述

文章目录OverviewHeterogeneous ComputingMPI (Message Passing Interface)ConcurrencyMulti-ProcessingIPCMulti-ThreadingCPUCPU InfoARM CPU featuresCPU BenchmarkSysbenchhtop压力测试CPU Instructions & IntrinsicsAssemblySIMDIntel MMX & SSEARM NEONConverter…

DoozyUI⭐️十六、Progressor Group:可视化帮手,进度条组自动求平均值

文章目录 🟥 效果展示🟧 实现过程1️⃣ 建立四个进度条2️⃣ 建立进度条组Progressor Group3️⃣ 运行看看效果吧Progressor Group,进度器组,它可以自动计算所有引用的进度器的进度值的平均值。 🟥 效果展示 下图Gif所示,下方的共5个进度条,下方的4个共同控制了最上…

redux太繁琐?一文入门学会使用mobx简化项目的状态管理

一、mobx基本介绍 mobx是一个简单、可扩展状态工具&#xff0c;相比redux&#xff0c;具有以下特点 简洁、无模板代码&#xff08;redux需要写大量模板代码&#xff09;响应式数据&#xff0c;可直接修改&#xff08;redux需要保证不可变&#xff09;可直接处理异步&#xff…

Ubuntu1604从0安装CUDA

港澳 2020年12月31日 目录通过安装CUDA来安装驱动先安装驱动禁用第三方驱动、卸载初始驱动、关闭图形界面再安装CUDA卸载CUDA安装Tensorflow安装VSCode和Requirements包InternalError: Dst tensor is not initialized实时监测GPU状态demo运行结果重要参考通过安装CUDA来安装驱动…

自动控制原理6.5---复合校正

参考书籍&#xff1a;《自动控制原理》(第七版).胡寿松主编. 《自动控制原理PDF版下载》 5.复合校正 5.1 按扰动补偿的复合校正 如果在系统的反馈控制回路中加入前馈通路&#xff0c;组成一个前馈控制和反馈控制相组合的系统&#xff0c;只要系统参数选择得当&#xff0c;既可…

Nginx -- SSL模块

目录 一、什么是ssl证书 二、证书的作用 1.浏览器绿色安全标志 2.网站数据加密传输 3.安全标志获得访客信任 4.强大的加密等级保障 5.SSL证书帮助网站保护了用户和网站之间的任何数据的安全 三、SSL证书的类别 1.按照验证方式 2.按照一张SSL证书保护的域名数 3.按照 …

虚拟土地+VRGDA:一种可持续的元宇宙土地销售策略

元宇宙可能刚刚开始形成&#xff0c;但虚拟土地危机已经存在 30 年了。 有啥问题&#xff1f;在稀缺性方面&#xff0c;让虚拟土地过于接近实体土地会导致经济问题&#xff0c;从而扼杀游戏和虚拟世界的发展。 我在今年早些时候写了一篇关于这个话题的文章&#xff0c;并指出…