聊聊伪共享

缓存--伪共享问题 伪共享问题指两个线程A和B,他们俩写入同一个cache block的不同变量时,会导致另一个cpu核心的缓存失效的问题。 解决伪共享有两种办法 一般而言,缓存行有64字节,我们知道一个long是8个字节,填充5个long之后,一共就是48个字节。 而 Java 中对象头在32位系统下占用8个字节,64位系统下占用16个字节,这样填充5个long型即可填满64字节,也就是一个缓存行。 @Contented 注解 cocurrenthashmap 中怎... 阅读详情

正文

CPU 如何读写数据的?

先来认识 CPU 的架构,只有理解了 CPU 的 架构,才能更好地理解 CPU 是如何读写数据的,对于现代 CPU 的架构图如下:

可以看到,一个 CPU 里通常会有多个 CPU 核心,比如上图中的 1 号和 2 号 CPU 核心,并且每个 CPU 核心都有自己的 L1 Cache 和 L2 Cache,而 L1 Cache 通常分为 dCache(数据缓存) 和 iCache(指令缓存),L3 Cache 则是多个核心共享的,这就是 CPU 典型的缓存层次。

上面提到的都是 CPU 内部的 Cache,放眼外部的话,还会有内存和硬盘,这些存储设备共同构成了金字塔存储层次。如下图所示:

从上图也可以看到,从上往下,存储设备的容量会越大,而访问速度会越慢。至于每个存储设备的访问延时,你可以看下图的表格:

你可以看到, CPU 访问 L1 Cache 速度比访问内存快 100 倍,这就是为什么 CPU 里会有 L1~L3 Cache 的原因,目的就是把 Cache 作为 CPU 与内存之间的缓存层,以减少对内存的访问频率。

CPU 从内存中读取数据到 Cache 的时候,并不是一个字节一个字节读取,而是一块一块的方式来读取数据的,这一块一块的数据被称为 CPU Line(缓存行),所以 CPU Line 是 CPU 从内存读取数据到 Cache 的单位 。

至于 CPU Line 大小,在 Linux 系统可以用下面的方式查看到,你可以看我服务器的 L1 Cache Line 大小是 64 字节,也就意味着 L1 Cache 一次载入数据的大小是 64 字节 。

那么对数组的加载, CPU 就会加载数组里面连续的多个数据到 Cache 里,因此我们应该按照物理内存地址分布的顺序去访问元素,这样访问数组元素的时候,Cache 命中率就会很高,于是就能减少从内存读取数据的频率, 从而可提高程序的性能。

但是,在我们不使用数组,而是使用单独的变量的时候,则会有 Cache 伪共享的问题,Cache 伪共享问题上是一个性能杀手,我们应该要规避它。

接下来,就来看看 Cache 伪共享是什么?又如何避免这个问题?

现在假设有一个双核心的 CPU,这两个 CPU 核心并行运行着两个不同的线程,它们同时从内存中读取两个不同的数据,分别是类型为 long 的变量 A 和 B,这个两个数据的地址在物理内存上是 连续 的,如果 Cahce Line 的大小是 64 字节,并且变量 A 在 Cahce Line 的开头位置,那么这两个数据是位于 同一个 Cache Line 中 ,又因为 CPU Line 是 CPU 从内存读取数据到 Cache 的单位,所以这两个数据会被同时读入到了两个 CPU 核心中各自 Cache 中。

我们来思考一个问题,如果这两个不同核心的线程分别修改不同的数据,比如 1 号 CPU 核心的线程只修改了 变量 A,或 2 号 CPU 核心的线程的线程只修改了变量 B,会发生什么呢?

分析伪共享的问题

现在我们结合保证多核缓存一致的 MESI 协议,来说明这一整个的过程,如果你还不知道 MESI 协议,你可以看我这篇文章「 10 张图打开 CPU 缓存一致性的大门 」。

①. 最开始变量 A 和 B 都还不在 Cache 里面,假设 1 号核心绑定了线程 A,2 号核心绑定了线程 B,线程 A 只会读写变量 A,线程 B 只会读写变量 B。

②. 1 号核心读取变量 A,由于 CPU 从内存读取数据到 Cache 的单位是 Cache Line,也正好变量 A 和 变量 B 的数据归属于同一个 Cache Line,所以 A 和 B 的数据都会被加载到 Cache,并将此 Cache Line 标记为「独占」状态。

③.  接着,2 号核心开始从内存里读取变量 B,同样的也是读取 Cache Line 大小的数据到 Cache 中,此 Cache Line 中的数据也包含了变量 A 和 变量 B,此时 1 号和 2 号核心的 Cache Line 状态变为「共享」状态。

④. 1 号核心需要修改变量 A,发现此 Cache Line 的状态是「共享」状态,所以先需要通过总线发送消息给 2 号核心,通知 2 号核心把 Cache 中对应的 Cache Line 标记为「已失效」状态,然后 1 号核心对应的 Cache Line 状态变成「已修改」状态,并且修改变量 A。

⑤. 之后,2 号核心需要修改变量 B,此时 2 号核心的 Cache 中对应的 Cache Line 是已失效状态,另外由于 1 号核心的 Cache 也有此相同的数据,且状态为「已修改」状态,所以要先把 1 号核心的 Cache 对应的 Cache Line 写回到内存,然后 2 号核心再从内存读取 Cache Line 大小的数据到 Cache 中,最后把变量 B 修改到 2 号核心的 Cache 中,并将状态标记为「已修改」状态。

所以,可以发现如果 1 号和 2 号 CPU 核心这样持续交替的分别修改变量 A 和 B,就会重复 ④ 和 ⑤ 这两个步骤,Cache 并没有起到缓存的效果,虽然变量 A 和 B 之间其实并没有任何的关系,但是因为同时归属于一个 Cache Line ,这个 Cache Line 中的任意数据被修改后,都会相互影响,从而出现 ④ 和 ⑤ 这两个步骤。

因此,这种因为多个线程同时读写同一个 Cache Line 的不同变量时,而导致 CPU Cache 失效的现象称为 伪共享( False Sharing ) 。

避免伪共享的方法

因此,对于多个线程共享的热点数据,即经常会修改的数据,应该避免这些数据刚好在同一个 Cache Line 中,否则就会出现为伪共享的问题。

接下来,看看在实际项目中是用什么方式来避免伪共享的问题的。

在 Linux 内核中存在 __cacheline_aligned_in_smp 宏定义,是用于解决伪共享的问题。

从上面的宏定义,我们可以看到:

  • 如果在多核(MP)系统里,该宏定义是 __cacheline_aligned ,也就是 Cache Line 的大小;

  • 而如果在单核系统里,该宏定义是空的;

因此,针对在同一个 Cache Line 中的共享的数据,如果在多核之间竞争比较严重,为了防止伪共享现象的发生,可以采用上面的宏定义使得变量在 Cache Line 里是对齐的。

举个例子,有下面这个结构体:

结构体里的两个成员变量 a 和 b 在物理内存地址上是连续的,于是它们可能会位于同一个 Cache Line 中,如下图:

所以,为了防止前面提到的 Cache 伪共享问题,我们可以使用上面介绍的宏定义,将 b 的地址设置为 Cache Line 对齐地址,如下:

这样 a 和 b 变量就不会在同一个 Cache Line 中了,如下图:

所以,避免  Cache 伪共享实际上是用空间换时间的思想,浪费一部分 Cache 空间,从而换来性能的提升。

我们再来看一个应用层面的规避方案,有一个 Java 并发框架 Disruptor 使用「字节填充 + 继承」的方式,来避免伪共享的问题。

Disruptor 中有一个 RingBuffer 类会经常被多个线程使用,代码如下:

你可能会觉得 RingBufferPad 类里 7 个 long 类型的名字很奇怪,但事实上,它们虽然看起来毫无作用,但却对性能的提升起到了至关重要的作用。

我们都知道,CPU Cache 从内存读取数据的单位是 CPU Line,一般 64 位 CPU 的 CPU Line 的大小是 64 个字节,一个 long 类型的数据是 8 个字节,所以 CPU 一下会加载 8 个 long 类型的数据。

根据 JVM 对象继承关系中父类成员和子类成员,内存地址是连续排列布局的,因此 RingBufferPad 中的 7 个 long 类型数据作为 Cache Line 前置填充 ,而 RingBuffer 中的 7 个 long 类型数据则作为 Cache Line  后置填充 ,这 14 个 long 变量没有任何实际用途,更不会对它们进行读写操作。

另外,RingBufferFelds 里面定义的这些变量都是 final 修饰的,意味着第一次加载之后不会再修改, 又 由于「前后」各填充了 7 个不会被读写的 long 类型变量,所以无论怎么加载 Cache Line,这整个 Cache Line 里都没有会发生更新操作的数据,于是只要数据被频繁地读取访问,就自然没有数据被换出 Cache 的可能,也因此不会产生伪共享的问题 。

无线通信中的‘隐身术’:深入浅出聊聊扩频技术与伪码同步的那些事儿 本文深入解析无线通信中的扩频技术与伪码同步原理,揭示其如何通过频谱展宽和伪随机码实现隐蔽、抗干扰的可靠传输。从军事应用到日常WiFi/蓝牙,扩频通信技术展现出强大的环境适应能力,特别是在数字调制解调和伪码同步方面的精妙设计,为现代无线通信提供了关键技术支撑。 阅读详情

相关推荐

C++ 避免伪共享:`alignas` 与填充字节的应用

当一个核心修改了缓存行中的数据,其他核心中包含相同缓存行的副本都需要失效或者更新。恰好位于同一个缓存行中,那么每次一个线程修改数据,都会导致另一个线程的缓存行失效,即使它们修改的是不同的数据!填充字节就是在数据结构中插入额外的字节,使数据位于不同的缓存行中。这玩意儿就像你和你的邻居共用一个水龙头,结果你洗菜的时候,他要洗车,互相影响,效率大打折扣。通过对比两个版本的运行时间,你可以看到避免伪共享可以显著提高并发计数器的性能。先别被“伪”字给忽悠了,这可不是假的共享,而是“假装”的共享。,但是可以自行添加。

weixin_41455464的博客 493

什么是伪共享(false sharing)

今天看go的sync.pool的代码,发现了一个比较陌生的名词 false sharing , 之前没听说过,就去查了下,瞬间学到了 type poolLocal struct { poolLocalInternal // Prevents false sharing on widespread platforms with // 128 mod (cache line s...

哇哦~ 1万+

信息收集-服务器信息

服务器上面可以运行大量的系统服务和第三方应用服务,如果操作系统或者第三方软件没有及时升级打补丁,攻击者就有可能直接通过服务器上运行的服务进行攻击。

weixin_49349476的博客 1446

伪共享(False Sharing)

目录 一、计算机的基本结构 二、缓存行 三、伪共享 四、如何避免伪共享 缓存系统中是以缓存行(cache line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。 一、计算机的基本结构 下图是计算的基本结构。L1、L2、L3分别表示一级缓存、二级缓存、三级缓存,越靠近CPU的缓存,速度越快,容量也越小。所以L1缓存很...

lyj2018gyq的博客 831

STM32 FATFS文件系统按行读取SD卡数据

STM32 SD数据实现按行读取

qq_32216815的博客 7828

NVIDIA GPU任务调度全景:从Block分配到Warp发射

GPU并行计算的核心是任务调度,而CUDA kernel在GPU上的执行并非简单的一次性操作,而是经过从CPU命令队列到硬件前端,再到GigaThread引擎和Warp Scheduler的多层协同。理解这一过程,是性能优化的基础。Warp作为硬件调度的基本单位,其状态切换与延迟隐藏能力直接决定计算单元的利用率;而Occupancy作为衡量SM上活跃Warp池大小的指标,也并非越高越好,需要结合访存模式与资源占用综合判断。对于多任务并发场景,Stream优先级、计算抢占、MIG硬件隔离与MPS软件共享等机制

weixin_32612263的博客 270

伪共享(false sharing),并发编程无声的性能杀手

共享(false sharing),并发编程无声的性能杀手 在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素。前段时间学习了一个牛X的高性能异步处理框架 Disruptor,它被誉为“最快的消息框架”,其 LMAX 架构能够在一个线程里每秒处理 6百万 订单!在讲到 Disruptor 为什么这么快时,接触到了一个概念——伪共享( false sharing ),其中提到:缓存行上的写竞争是运行在 SMP 系统中并行线

felby的博客 1029

微信DAT图片解密全攻略:一键转换JPG/PNG/GIF

本文详细解析了微信电脑版中DAT图片的加密原理与存储路径,并提供了从定位文件到使用专业工具进行一键解密的完整操作指南。通过简单的步骤,用户可将加密的DAT文件无损还原为JPG、PNG或GIF格式,有效解决无法直接查看聊天图片的难题,同时兼顾了隐私清理与磁盘空间管理。

hill8的博客 1368

【并发编程】 --- 伪共享(False Sharing)底层原理及其解决方式

文章目录1 什么是伪共享1.1 从疑问说起 --- 难道JMM中不存在局部性原理???1.2 缓存行的概念1.3 伪共享(False Sharing)的概念 + 其可能引发的性能问题2 如何避免伪共享 --- 数据填充2.1 不使用数据填充时的效率验证2.2 手动进行数据填充的效率验证2.3 通过java8新特性@sun.misc.Contended注解进行数据填充的效率验证 源码地址:ht...

nrsc 3571

如何读懂CiteSpace图谱?科研小白必看的节点颜色/年轮/聚类解读手册

本文详细解析CiteSpace图谱的核心视觉元素,包括节点颜色、年轮和聚类标识,帮助科研人员准确解读数据分析结果。通过生物医学案例展示如何从图谱中识别关键文献和研究趋势,提供五步系统解读法和常见错误规避策略,助力科研小白快速掌握文献计量分析技巧。

weixin_29179311的博客 318

城市仿真软件:AnyLogic_(8).行人与人群动态仿真

定义行人个体的行为和属性,如行走速度、目标位置等。// 创建一个行人// 设置行走速度为1.5 m/s// 设置目标位置为入口// 创建另一个行人// 设置行走速度为1.2 m/s// 设置目标位置为出口// 创建行人// 创建乘客行人与人群动态仿真在城市规划、交通管理和紧急疏散等领域具有广泛的应用。通过AnyLogic提供的行人模型模块,可以有效地模拟行人的个体行为、群体动态和与环境的交互。

kkchenjj的博客 1250

java中什么是 伪共享_【Java】聊聊多线程中的伪共享现象

首页专栏java文章详情0聊聊多线程中的伪共享现象小强大人发布于 1 月 27 日什么是伪共享?讲伪共享之前,让我们先乘坐时光机,回到大学课堂,来重温下计算机组成原理的基础知识。我们知道,CPU和内存的运行速度相差很大,为了解决这个问题,在CPU和内存之间会加一级或多级高速缓存(Cache)。这个Cache一般是集成在CPU内部的,所以也叫CPU Cache。下图是一个两级Cache的CPU-Ca...

weixin_32287433的博客 307

购物车智能检测系统:YOLO26 + PyQt5 毕设实现

YOLO26 + PyQt5 完成购物车商品检测系统:标注数据集(OpenImages 筛选 + 自制 500 张),训练 100 epoch 得到 mAP50 约 0.89,集成商品定价后输出总价。建议先用 OpenImages 预训练权重在自制数据上调优(冻住 backbone 前 50 epoch),等 loss 稳定后再解冻全量训练。

m0_64661770的博客 56

聊聊CPU缓存、伪共享与缓存行填充

前言 2019年的最后一篇技术文了(明天写总结),找个细碎的话题随便聊聊吧。本文的知识点在之前的文章中隐蔽地出现过。 CPU缓存简介 CPU高速缓存(cache)是现代中央处理器的必备部件。它位于CPU package内部,在金字塔形存储体系中,距离计算单元的距离是各存储部件中第二近的,存取速度也是第二快的,仅次于寄存器。 CPU在读内存时,会先尝试从缓存读取,如果缓存未命中,再从...

LittleMagic's Blog 962

15、导频辅助信道估计:挑战与解决方案

本文探讨了导频辅助信道估计在双选择性信道中的挑战,分析了传统方法的局限性,并提出了一种新的导频子载波结构。该结构综合考虑相干频率带宽与相干时间,通过动态移动导频位置并结合时间与频率分集,提升了高速移动环境下的信道估计性能。文章还讨论了其硬件实现、系统开销及兼容性等实际问题,并展望了优化策略、降低开销及与其他技术融合的未来研究方向。

o4p5q6r7s的博客 103

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

聊聊java8中的@sun.misc.Contended与伪共享

“持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情”@[toc] 在前面学习ConcurrentHashMap的size方法的过程中,对于CounterCell这个类,有个特殊的注解 @sun.misc.Contended。那么今天就来聊聊这个注解的具体作用和其底层的基本原理。 经过查阅资料之后,不难发现,@sun.misc.Contended注解是被设计用来解决伪共享问题的。那么什么是为伪共享呢?在了解伪共享之前,需要了解一下CPU的缓存机制。1.1 CP

jjc4261的博客 1102

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

java是伪多线程么,聊聊多线程中的伪共享现象

什么是伪共享?讲伪共享之前,让我们先乘坐时光机,回到大学课堂,来重温下计算机组成原理的基础知识。我们知道,CPU和内存的运行速度相差很大,为了解决这个问题,在CPU和内存之间会加一级或多级高速缓存(Cache)。这个Cache一般是集成在CPU内部的,所以也叫CPU Cache。下图是一个两级Cache的CPU-Cache-内存架构。数据在Cache中是按行存储的,其中每一行称为一个Cache行,...

weixin_33125137的博客 321

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

聊聊伪SRE

序言 剑就是剑,木剑铁剑并没有什么差别,如果提出的问题本身就有问题,那么答案又有什么意义呢?时光荏苒,还没过多久,各种高级词汇就逐渐的淡出视野,现在已经没人说碎片化时间了,因为大家都有在忙的时间,无数的短视频已经充斥了所有的时间。伪SRE 曾几可时,SRE的概念开始风靡,每个团队都自称为SRE,扛着一个名号,就感觉自己认为很高档很高级,但是实际上,名称虽然改变了,但是手头上的事还是...

运维Linux和python 481

Cache伪共享

cache伪共享

saturn254的专栏 284

JAVA并发编程中伪共享问题与缓存行对齐的优化策略

在多核处理器架构下,每个核心都有自己的高速缓存(Cache)。当多个线程同时访问位于同一缓存行(Cache Line)的不同变量时,即使这些变量之间没有逻辑上的依赖关系,也会因为缓存一致性协议而导致性能下降,这就是伪共享。1.1 缓存行(Cache Line)缓存行是CPU缓存中最小的存储单元,通常大小为64字节(在某些架构上可能是32或128字节)。当CPU访问内存中的某个数据时,会将包含该数据的整个缓存行加载到缓存中。1.2 缓存一致性协议。

weixin_41455464的博客 636

为什么你的代码跑不快?降低嵌入式Linux性能优化的最隐秘杀手——伪共享与缓存缺失

在资源受限的嵌入式 Linux 系统中,数据布局(Data Layout)往往比算法逻辑更重要。变链表为数组:利用内存的连续性触发预取。对齐与填充:用空间换时间,彻底消灭伪共享。减少跳转:让代码的执行流和数据的访问流尽可能的“平滑”。别让 CPU 把时间浪费在等待 RAM 上,喂饱它的 Cache,你的程序才能真正飞起来。

weixin_67351993的博客 916

聊聊sessionStorag,以及什么情况下能够共享/复制sessionStroage

1.sessionStorage完全不能在两个窗口共享吗?一般来说,在不同窗口sessionStorage是隔离的,互不影响。但是通过window.open(url,‘_blank’)在新窗口打开同域下的地址,新开的窗口会复制当前窗口的sessionStorage为新窗口的sessionStorage,但是后续两个窗口的sessionStorage互不影响。

lingliu0824的博客 1454

聊聊跨进程共享内存的内部工作原理

值得注意的是,这个文件并非是磁盘上的文件,而只是在内存里的。在 Linux 系统的进程虚拟内存中,一个重要的特性就是不同进程的地址空间是隔离的。这个方案在传输的数据量较小的时候工作是很不错的。内核中它的主要逻辑有两个,一是调用 get_unused_fd_flags 申请一个没使用过的文件句柄,二是调用 shmem_file_setup 创建一个共享内存文件。但如果进程间想共享的数据特别大,比如说几个 GB,那如果使用网络 IO 方案的话,就会涉及到大量的内存拷贝的开销,导致比较低的程序性能。

youzhangjing_的博客 1598

聊聊非用户内存的数据共享

结合这两篇文章可以知道,ABAP内存能共享在同一用户的同一窗口,SAP内存能共享在同一用户不同窗口,SHARED BUFFER / MEMORY和DATABASE能共享在不同用户,不同客户端。以上就是关于非用户内存进行数据共享的介绍,希望对你有所帮助。

BinGeneral的博客 548
上一篇: 聊聊Redis内存优化的7个神技
下一篇: 简单了解---JVM
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值