vc++64位系统下long的长度为4个字节_Java与系统硬件的亲密接触「伪共享」

作者:码洞来源:https://zhuanlan.zhihu.com/p/32764602
3df1f6022427caa5689bc2b7f3c8b3db.png

在解释【伪共享】这个概念之前,我们先来运行一段代码,小编的电脑上有4个core。

604f997cb4443f23159bcbc07820695c.png

这个程序的逻辑是4个线程共享同一个数组读写不同下标的变量。每个线程循环1亿次读写,也就是+1操作。然后统计4个线程同时跑完总共花的时间。

af61ee941bd6ede6d2ae26d2390f864b.png

下面我们来看看在小编的电脑上运行的结果:

378b1b76dc539bd45917bced43958a17.png

然后我把SharingLong里面的注释代码去掉,再跑了一下:

ba37e285fc8cfe0eefe2f180a79a5766.png

在性能上注释前后差别高达5比1,为什么会在性能上会产生如此大的差别呢?

这就是本篇要讲的主题【伪共享】,英文名叫False Sharing。而SharingLong里面的注释行一般称之为【缓存行填充】,英文名叫Cache Line Padding。

首先我们来计算一下SharingLong对象占用的内存空间,我们不考虑64位的情景,Java的对象都有一个2个word的头部,第一个word存储对象的hashcode和一些特殊的位标志,如GC的分代年龄、偏向锁标记等,第二个word存储对象的指针地址,一个word就是32位。然后加上v和6个p变量,总共就是8个long的长度,也就是64字节。

接下来我们要引入CPU缓存的概念。

74abff1f3d8a78f85abd13fc68e322a0.png

现代的处理器一般都有3级缓存结构,L1、L2和L3,CPU直接访问主存是一个相对比较慢的操作,所以通过3级缓存来提升访存性能。我们将3个缓存当成一个整体来看待,它就是CPU缓存。缓存的制造成本非常昂贵,它一般要比主存空间小的多。

CPU在读主存的时候,会先将主存的一块数据加载到缓存上,然后在缓存上读取。当CPU写主存的时候,它会首先写缓存,在未来的某个时间点再一次性将缓存的数据全部刷回主存,这样就可以提高写操作的性能。因为计算机程序数据操作的局部性,CPU连续的指令倾向于访问相邻地址空间的数据,所以后续的读写操作有很大的概率可以直接在缓存上拿到数据。如果缓存上不存在,那就再去主存上加载进来。

缓存虽然小,但是也不是太小,CPU在加载主存数据时,如果一次性将整个Cache填满,但是接下来的指令访问的数据又不在缓存上,就会导致读浪费。另外如果只修改了其中几个字节的数据,但是得回写整个Cache到内存,这又会导致写浪费。

所以现代的CPU缓存一般是分行存储的,最小处理单位是一个行,这个行的长度一般来说就是上文提到的64字节,我们称之为【缓存行】。

4f224d4a82e5c972f26c49394a1161f7.png

SharingLong对象中v的值是volatile类型的,意味着CPU要保证v变量在不同线程之间的读写可见行。当CPU对v变量进行修改的时候会将数据立即回写至主存并将相应的缓存行置为失效。这样后续对v变量进行的读写操作都需要重新从内存中加载缓存行,这样就保证了其它线程读到的数据是最新的。

这点跟我们平常在Java基础教科书里提到的有点不一样。教科书里面为了便于新手理解,不会提及缓存,一般只会说volatile变量直接读写内存。

如果内存里有两个volatile变量在相邻的地址,两个cpu分别对v1和v2进行读和写操作,会发生什么情况呢?首先我们分解执行动作。图中的h表示对象头。

fd022511d4cfdfd80f58ff5b355ba726.png

1、CPU1对v1进行读操作,将内存里的v1加载到缓存行里。

2、CPU2对v2进行读操作,将内存里的v2加载到缓存行里。

3、CPU1对v1进行写操作,将缓存里的v1修改,然后回写到主存再将缓存行置为失效。

4、CPU2对v2进行写操作,将缓存里的v2修改,然后回写到主存再将缓存行置为失效。

步骤1肯定先于步骤3,步骤2肯定先于步骤4。它们发生的顺序可能是 1->2->3->4 ,相当于两个CPU交叠运行,步骤1加载缓存行,步骤2发现数据就在缓存行里还是最新的,就省去了加载缓存行操作了,这时读操作做到了【共享】。紧接着步骤3正常进行写操作,然后步骤4来了,CPU2发现缓存行失效了,所以还得重新加载缓存行,然后再回写到主存再将缓存行置为失效。这里就发生了重复加载缓存行的现象,也即【写竞争】。如果不是volatile变量,步骤3的写操作是不会立即回写内存的,缓存行也就不会立即置为失效,这个时候步骤4来了CPU可以直接对缓存进行写操作,而不会出现浪费现象。我们称这种现象为【伪共享】,就是说这两个变量虽然共享同一个缓存行,但是它们之间会发生写竞争。

如果顺序是1->3->2->4,步骤1和步骤3的读操作这时就没能实现共享,还是会有浪费。

当系统的线程数越多时,写竞争越激烈,这种浪费就越多。

现在我们能明白为什么去掉注释后,程序会变慢,因为存在写竞争现象,数组中相邻的SharingLong.v共享了同一个缓存行。

那加上p1~p6这6个变量的意义是什么呢?我们看图。

20cbe7258235e7229e77d6625a38feee.png

我们发现加上6个long变量后,v1和v2将分别占用自己的缓存行,互不干扰,所以写竞争也就不存在了,效率自然就提升了。

不过缺点也是有的,就是缓存的利用率降低了,一个缓存行的空间才使用了1/4。这就是典型的空间换时间的场景。

例子中我们使用了volatile变量,那如果改成普通变量呢?我们运行一下,结果如下。

9c70c10eeff1c7c71ad53e78fb33527e.png

相当惊人,耗时上居然少了3个量级,这就是volatile在性能上的代价。普通变量不需要保证线程之间的读写的可见性,CPU对缓存修改后不需要立即回写内存,不存在写操作缓存穿透现象。而读操作也不需要总是重新从内存加载,那这个效率几乎完全就是缓存访问的效率,而对volatile变量的读写操作则接近内存访问的效率,差距自然如此明显。

你也许会问,知道这些有什么蛋用!

确是没什么蛋用,因为在现实世界,大部分操作都涉及到IO操作。根据水桶效应,其它环节优化到了极致,也无法提升整体的质量。

但是也不完全所有的应用都是IO操作型的,有一些场景下那是纯粹的内存操作。那么对于纯内存操作来说,理解【伪共享】知识可以帮你从性能上提升几倍甚至是几个数量级。

著名的disruptor框架正是使用了缓存行填充技术,才使得它的环形数组队列能如此高效。看wiki上的性能报告,disruptor的RingBuffer相比Java内置的ArrayBlockingQueue在OPS上高出近一个数量级,在队列延迟上则低了接近3个数量级。

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

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

相关文章

linux 端口号查看

linux 端口号查看netstat -anp |grep 端口号最后一列是端口号转载于:https://www.cnblogs.com/ditmark/p/7669804.html

pwm波如何控制电机代码_PWM波控制720电机

详细方案四:pwm与720电机控制电机硬件分析什么是电机?电机(俗称"马达")是指依据电磁感应定律实现电能转换或传递的一种电磁装置。它的主要作用是产生驱动转矩,作为用电器或各种机械的动力源。其中本四轴采用的电机是直流电机。直流…

arduino使用oled代码_【惊不?】Arduino改造古董卡西欧计算器为作弊神器

一台朴实无华的计算器,卡西欧牌。看起来和千万学子们所使用的计算器一样,没什么特别的,还带着一些陈旧的使用痕迹。但如果你用磁铁碰一下:咦,简直像打开了探险电影里的神秘机关,右上角的太阳能电池板部分居…

python字符串数组_python将字符串转换成数组的方法

Python这篇文章主要介绍了Python __setattr__、 __getattr__、 __delattr__、__call__用法示例,本文分别对这几个魔法方法做了讲解,需要的朋友可以参考下 ... junjie4882019-11-22 Python这篇文章主要介绍了Python实现list反转的方法,实例总结了关于list的各种较为常见的操作技…

vsc写vue生成基本代码快捷键_基于vue2.X的webpack基本配置,教你手动撸一个webpack4的配置...

webpack说复杂也不复杂。不复杂,核心概念不外乎是entry, output, loader, plugins。webpack4还新增了optimization选项,用于代码分割和打包优化。现在webpack官网文档已经写的挺棒了。而当你真正开始手写一个webpack.config.js的时候,你就会发…

几款效率神器助你走上人生巅峰

一、背景 在我的工作和生活中,我一直都很注重效率工具的使用,这么些年下来也积累好几款很不错,但是又不为大多数人所熟知的软件工具,我用起来得心应手,很不错。那我就像在此给大家做个分享,希望你们都能多多…

macos下载的安装包在哪里_macbook任意降级,为您带来mac os完美降级教程

最近有许多用户反映升级了最新的系统出现了各种奇葩问题,更关键的是,很多常用(专业)软件不能使用了。因此有许多用户想要降级,现在macdown小编为您带来mac os完美降级教程,有需要的赶紧收藏啦!第…

kodi刮削器 中文_教你PLEX插件播放4K不能使用KODI解码导致卡顿的解决办法

文章作者Hao4K用户nothero自从买了这个索尼9500g,三天两头的出问题,而且还经常重启,经常卡顿。大法的画质确实不错,但是这系统稳定性实在是不敢恭维...最近装了一个KODI-PLEX插件,安利安利{:4_96:},画面实在…

玩下软工项目,第一轮--全局Context的获取,SQLite的建立与增删改查,读取用户通话记录信息...

项目的Github地址:https://github.com/ggrcwxh/LastTime 采用基于git的多人协作开发模式 软件采用mvc设计模式,前端这么艺术的事我不太懂,交给斌豪同学去头疼了。第一轮先实现查询通话记录返回对应号码上一次的通话时间。 真机测试下的效果图…

jvm垃圾回收机制_深入理解JVM的垃圾回收机制

​如何判断对象已“死”Java堆中存放着几乎所有的对象实例,垃圾回收器在堆进行垃圾回收前,首先要判断这些对象那些还存活,那些已经“死去”。判断对象是否已“死”有如下几种算法:1引用计数法引用计数法描述的算法为:给…

chown -r oracle:oinstall /oracle,CentOS7安装Oracle12c图文详解

root身份安装依赖包: yum -y install binutils compat-libcap1 compat-libstdc-33 compat-libstdc-33*.i686 elfutils-libelf-devel gcc gcc-c glibc*.i686 glibc glibc-devel glibc-devel*.i686 ksh libgcc*.i686 libgcc libstdc libstdc*.i686 libstdc-devel li…

在苹果笔记本如何使用python_mac下如何将python2.7改为python3

1.查看当前电脑python版本 python -V // 显示2.7.x 2.用brew升级python brew update python 3.如果安装成功,去系统目录下回看到两个版本的python cd usr/local/Cellar/ //到此目录下 cd python/ //进入python目录下 查看已安装的python版本,如果有2.x 和…

[BZOJ1834][ZJOI2010]network 网络扩容 最大流+费用流

1834: [ZJOI2010]network 网络扩容 Time Limit: 3 Sec Memory Limit: 64 MB Submit: 3330 Solved: 1739 [Submit][Status][Discuss]Description 给定一张有向图,每条边都有一个容量C和一个扩容费用W。这里扩容费用是指将容量扩大1所需的费用。求: 1、 …

python需要花钱下载吗_用Python下载知乎视频,非常实用

原标题:用Python下载知乎视频,非常实用Python下载知乎视频。 # -*- coding: utf-8 -*- """ 下载知乎视频: 依赖: pip install requests mac 安装 ffmpeg: brew install ffmpeg """ import re impor…

python相比于excel的优势_对照Excel使用Python进行数据分析,更快掌握

Excel和Python,作为数据分析的主流工具,在从效率提升到数据商业化的整个过程中,都起到了重要作用。不管是在Excel中通过鼠标点选实现,亦或是利用Python通过代码实现,数据分析中的很多基础功能都是相通的。 在数据量级大…

python二维图颜色函数_Python scipy的二维图像卷积运算与图像模糊处理操作示例

本文实例讲述了Python scipy的二维图像卷积运算与图像模糊处理操作。分享给大家供大家参考,具体如下: 二维图像卷积运算 一 代码 import numpy as np from scipy import signal, misc import matplotlib.pyplot as plt image misc.ascent()#二维图像数组…

linux命令行大全 笔记,《Linux命令行大全》读书笔记

8种机械键盘轴体对比本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?shell就是一个程序,它接受从键盘输入的命令,然后把命令传递给操作系统执行。当使用图像用户界面时,我们需要一个叫做终端仿真器…

dedecms 添加,编辑文章时 文章标题有字数限制的解决办法。

百度上说 解决方法有两步: 一、先要在系统参数下的其他选项中修改文档标题最大长度(如修改为150), 后台系统 - 其它选项 - 文档标题最大长度 默认的是60改为150, 二、修改默认参数后,需要手工修改数据表。 …

python绘制散点图、如何选两列作为横坐标_在matplotlib散点图(水平杆图)中从x=0到数据点绘制水平线...

考虑以下情节:由此函数生成:def timeDiffPlot(dataA, dataB, savetoNone, legNone): labels list(dataA["graph"]) figure(figsizescreenMedium) ax gca() ax.grid(True) xi range(len(labels)) rtsA dataA["running"] / 1000.0…

python运行命令_Python中执行外部命令

有很多需求需要在Python中执行shell命令、启动子进程,并捕获命令的输出和退出状态码,类似于Java中的Runtime类库。 subprocess模块的使用: Python使用最广泛的是标准库的subprocess模块,用来替换os.system(),os.spawn*(),os.popen…