什么是伪共享(false sharing)

2023-11-11

今天看go的sync.pool的代码,发现了一个比较陌生的名词 false sharing , 之前没听说过,就去查了下,瞬间学到了

type poolLocal struct {
   poolLocalInternal

   // Prevents false sharing on widespread platforms with
   // 128 mod (cache line size) = 0 .
   pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}

什么是false sharing

这里需要解决这几个问题

(1)什么是cpu缓存行

(2)什么是内存屏障

(3)什么是伪共享

(4)如何避免伪共享

CPU缓存架构

cpu是计算机的心脏,所有运算和程序最终都要由他来执行。

主内存RAM是数据存在的地方,CPU和主内存之间有好几级缓存,因为即使直接访问主内存相对来说也是非常慢的。

如果对一块数据做相同的运算多次,那么在执行运算的时候把它加载到离CPU很近的地方就有意义了,比如一个循环计数,你不想每次循环都到主内存中去取这个数据来增长它吧。

越靠近CPU的缓存越快也越小

所以L1缓存很小但很快,并且紧靠着在使用它的CPU内核。

L2大一些,但也慢一些,并且仍然只能被一个单独的CPU核使用

L3在现代多核机器中更普遍,仍然更大,更慢,并且被单个插槽上的所有CPU核共享。

最后,主内存保存着程序运行的所有数据,它更大,更慢,由全部插槽上的所有CPU核共享。

当CPU执行运算的时候,它先去L1查找所需的数据,再去L2,然后L3,最后如果这些缓存中都没有,所需的数据就要去主内存拿。

走得越远,运算耗费的时间就越长。所以如果进行一些很频繁的运算,要确保数据在L1缓存中。

CPU缓存行

缓存是由缓存行组成的,通常是64字节(常用处理器的缓存行是64字节的,比较旧的处理器缓存行是32字节的),并且它有效地引用主内存中的一块地址。

一个java的long类型是8字节,因此在一个缓存行中可以存8个long类型的变量

在程序运行的过程中,缓存每次更新都从主内存中加载连续的64个字节。因此,如果访问一个long类型的数组时,当数组中的一个值被加载到缓存中时,另外7个元素也会被加载到缓存中。但是,如果使用的数据结构中的项在内存中不是彼此相邻的,比如链表,那么将得不到免费缓存加载带来的好处。

不过,这种免费加载也有一个坏处。设想如果我们有个long类型的变量a,它不是数组的一部分,而是一个单独的变量,并且还有另外一个long类型的变量b紧挨着它,那么当加载a的时候将免费加载b。

看起来似乎没有什么问题,但是如果一个cpu核心的线程在对a进行修改,另一个cpu核心的线程却在对b进行读取。当前者修改a时,会把a和b同时加载到前者核心的缓存行中,更新完a后其它所有包含a的缓存行都将失效,因为其它缓存中的a不是最新值了。而当后者读取b时,发现这个缓存行已经失效了,需要从主内存中重新加载。

请记着,我们的缓存都是以缓存行作为一个单位来处理的,所以失效a的缓存的同时,也会把b失效,反之亦然。

这样就出现了一个问题,b和a完全不相干,每次却要因为a的更新需要从主内存重新读取,它被缓存未命中给拖慢了。这就是传说中的伪共享。

伪共享

当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

public class FalseSharingTest {

    public static void main(String[] args) throws InterruptedException {
        testPointer(new Pointer());
    }

    private static void testPointer(Pointer pointer) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.x++;
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                pointer.y++;
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(System.currentTimeMillis() - start);
        System.out.println(pointer);
    }
}

class Pointer {
    volatile long x;
    volatile long y;
}

上面这个例子,我们声明了一个Pointer的类,它包含了x和y两个变量(必须声明为volatile,保证可见性,关于内存屏障的东西我们后面再讲),一个线程对x进行自增1亿次,一个线程对y进行自增1亿次。

可以看到,x和y完全没有任何关系,但是更新x的时候会把其它包含x的缓存行失效,同时y也就失效了,运行这段程序输出的时间为3890ms。

如何避免

伪共享的原理我们知道了,一个缓存行是64字节,一个long类型是8个字节,所以避免伪共享也很简单,大概有以下三种方式:

(1)在两个long类型的变量之间再加7个long类型

我们把上面的pointer改成下面这个结构

class Pointer {
    volatile long x;
    long p1, p2, p3, p4, p5, p6, p7;
    volatile long y;
}

再次运行程序,会发现输出时间神奇的缩短为695ms

(2)重新创建自己的long类型,而不是java自带的long修改Pointer如下

class Pointer {
    MyLong x = new MyLong();
    MyLong y = new MyLong();
}

class MyLong {
    volatile long value;
    long p1, p2, p3, p4, p5, p6, p7;
}

同时把pointer.x++改为pointer.x.value++;等,再次运行程序发现时间是724ms,这样本质上还是填充。

(3)使用@sun.misc.Contended注解(java8)

修改MyLong如下:

@sun.misc.Contended
class MyLong {
    volatile long value;
}

默认使用这个注解是无效的,需要在JVM启动参数加上-XX:-RestrictContended才会生效,再次运行程序发现时间是718ms。注意,以上三种方式中的前两种是通过加字段的形式实现的(上面go代码里的实现也是这样的),加的字段又没有地方使用,可能会被jvm优化掉,所以建议使用第三种方式。

内存屏障

1.volatile是一个类型修饰符,volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略。

2.volatile的特性:

(1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其它线程来说是立即可见的-》实现可见性

(2)禁止进行指令重排序(实现有序性)

(3)volatile只能保证对单次读写的原子性。i++这种操作不能保证原子性

3.volatile的实现原理中的可见性就是基于内存屏障实现

内存屏障(Memory Barrier):又称内存栅栏,是一个CPU指令。

在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JVM为了保证在不同的编译器和CPU上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和CPU:不管什么指令都不能和这条内存屏障指令重排序

总结

(1)CPU具有多级缓存,越接近CPU的缓存越小也越快

(2)CPU缓存中的数据是以缓存行为单位处理的;

(3)CPU缓存行能带来免费加载数据的好处,所以处理数据性能非常高

(4)CPU缓存行也带来了弊端,多线程处理不相干的变量时会相互影响,也就是伪共享

(5)避免伪共享的主要思路就是让不相干的变量不要出现在同一个缓存行中;

1是每两个变量之间加上7个long类型;2是创建自己的long类型,而不是用原生的;3是使用java8的注解

 

参考链接:

https://www.jianshu.com/p/64240319ed60

https://www.jianshu.com/p/7758bb277985

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

什么是伪共享(false sharing) 的相关文章

  • 无法在 Golang 中导入本地模块

    我正在尝试导入本地模块 但无法使用以下命令导入它go mod 我最初使用以下方式构建了我的项目go mod init github com AP Ch2 GOMS 注意我的环境是go1 14我使用 VSCode 作为我的编辑器 这是我的文件
  • 记录 http.ResponseWriter 内容

    Premise 我发现了类似的问题 但不适用于我的情况 因此请不要将其标记为重复 我在 Go 中有一个 HTTP 服务器 并且创建了一个中间件记录请求 响应时间 我也想记录响应 我用过httputil DumpRequest在一个名为的函数
  • go build 不断抱怨:go.mod 有 post-v0 模块路径

    Go 1 11 发布后 我一直在尝试将我的存储库移动到 Go 模块 方法是添加go mod文件在其根目录下 我的根库之一my host root其版本为17 0 1 所以我在其中写道go mod file module my host ro
  • 将 Websocket 消息发送到 Go 中的特定通道(使用 Gorilla)

    我对 Go 很陌生 并且发现自己使用套接字作为我的第一个项目 这是一个多余的问题 但我无法理解如何将 websocket 更新发送到 Go 中的特定通道 使用 Gorilla 我在用此链接中的代码示例 https github com go
  • 是否可以在 Golang 中 pickle 结构实例

    我正在 Golang 中做一些机器学习 我现在碰壁了 我训练有素的分类器需要将近半分钟的时间来训练 并且想要保存分类器的该实例 这样我就不必每次都从头开始训练 在 Golang 中应该如何去做呢 仅供参考 我的分类器是一个结构 当我用 py
  • Golang 从管道读取读取大量数据

    我正在尝试读取一个正在被焦油化 流式传输到标准输入的存档 但我正在以某种方式读取far管道中的数据多于 tar 发送的数据 我像这样运行我的命令 tar cf somefolder my go binary 源代码是这样的 package
  • 无法从另一个标签的源代码构建和安装 go

    我正在尝试使用此从源代码构建和安装 go文档 https go dev doc install source 当我喜欢以下内容时 这效果很好 git clone https go googlesource com go goroot cd
  • 限制 FormFile 中的文件大小

    我让用户使用 FormFile 上传文件 我应该在什么时候检查文件大小是否太大 当我做 file header fileErr r FormFile file 文件对象已经创建 那么我是否已经产生了读取整个文件的成本 https golan
  • 将 []string 传递给需要可变参数的函数

    为了不一遍又一遍地重复我的自我 我想创建一个处理运行一些命令的函数 func runCommand name string arg string error cmd exec Command name arg if err cmd Run
  • 从 Go Slice 中选择一个随机值

    情况 我有一些值 需要从中随机选择一个值 然后我想将它与固定字符串连接起来 到目前为止 这是我的代码 func main create the reasons slice and append reasons to it reasons m
  • 数据库连接最佳实践

    我有一个使用 net http 的应用程序 我使用 http 注册了一些处理程序 这些处理程序需要从数据库中获取一些内容 然后才能继续编写响应并完成请求 我的问题是连接到该数据库的最佳实践是什么 我希望它能够以每分钟 1 个请求或每秒 10
  • runtime.LockOSThread 是否允许子 goroutine 在同一个操作系统线程中运行?

    我明白在 Go 中 runtime LockOSThread https golang org pkg runtime LockOSThread将一个 goroutine 绑定到一个操作系统线程 并且不允许其他 goroutine 在该线程
  • GO并发编程测试

    我试图确保我的并发程序不存在以下情况 僵局 livelock 饥饿 我找到了以下工具http blog golang org race detector http blog golang org race detector 我尝试编译并运行
  • Golang 按位运算以及一般字节操作

    我有一些 C 代码 可以对字节执行一些按位运算 我正在尝试在 golang 中做同样的事情 但遇到了困难 C 中的示例 byte a c byte data int j c data j c byte c j c a c 0xFF c 0x
  • 视频第一帧

    我正在创建一个单页应用程序 后端使用 Golang 前端使用 javascript 我想找到一种使用 Golang 获取视频第一帧的方法 首先 我将 mp4 视频文件上传到服务器 它保存在服务器上 有没有办法使用 Golang 获取该视频的
  • 按引用或按值扫描功能

    我有以下代码 statement SELECT id from source where mgmt 1 var exists string errUnique dr db QueryRow statement mgmt Scan exist
  • Golang 基础知识 struct 和 new() 关键字

    我正在学习 golang 当我阅读描述结构的章节时 我遇到了初始化结构的不同方法 p1 passport var p2 passport p3 passport Photo make byte 0 0 Name Scott Surname
  • 在函数中将通道作为参数传递的不同方法

    我正在阅读一些Go代码 并说了几种传递Go通道的不同方法 也许它们是相同的 但我想知道是否有任何区别 因为我无法在线找到文档 1 func serve ch lt chan interface do stuff 2 func serve c
  • 在 IntelliJ IDEA 中运行。多个文件和错误未定义:数据

    我想使用 IntelliJ IDE 社区版编写代码GO Go语言 我安装了正确的插件 并安装了构建应用程序所需的所有工具 我的应用程序包含以下两个文件 每个都在目录中 事件服务器 Main go Data go 如果我想使用 Run Ctl
  • Gorm 总是返回带有 nil 值的结构

    我正在使用 Gorm 构建 Go Web API 作为 Amazon RDS 中 Postgresql 数据库的 ORM 问题是 Gorm 总是返回一片结构 其值全部为零 尽管数据库已经填充了数据 切片中的结构体数量是否合适取决于LIMIT

随机推荐

  • C++11-14 第9讲 Alias Template(化名)

    template
  • 接口测试——PyTest自动化测试框架(八)

    1 PyTest介绍与安装 PyTest介绍 PyTest是python的一个第三方的单元测试库 自动识别测试模块和测试函数 支持非常丰富的断言 assert 语句 PyTest中的使用约束 测试文件的文件名必须以 test 或 test
  • Java项目 log4j2 配置日志写入指定文件

    一 背景 由于业务需要 需要将服务部分埋点日志写入指定文件 然后进行日志收集 进行数据分析统计 需要通过修改log4j2配置 引入对应logger打印日志实现 二 log4j2 xml配置
  • java 代理(静态代理、动态代理的不同实现)详解及示例

    文章目录 一 代理构成 1 代理介绍 2 应用场景介绍 二 静态代理 1 示例 1 售票服务 2 售票 3 代售点服务 4 静态代理实现 1 maven 依赖 2 实现 三 动态代理 1 InvocationHandler角色 2 Invo
  • 2023年高教社杯数学建模国赛C题详细版思路

    C 题 蔬菜类商品的自动定价与补货决策 2023年国赛如期而至 为了方便大家尽快确定选题 这里将对C题进行解题思路说明 以分析C题的主要难点 出题思路以及选择之后可能遇到的难点进行说明 方便大家尽快找到C题的解题思路 难度排序 B gt A
  • 常见的防火墙有哪几种类型

    防火墙对于游戏 金融 视频等等易受到攻击的行业来说 其部署是相当重要的 虽说不能百分百防御所有攻击 但在其中也起了很大的作用 防火墙是为加强网络安全防护能力在网络中部署的硬件设备 有多种部署方式 常见的主要有以下几种方式 1 桥模式 桥模式
  • STL之二级空间配置器及实现

    之前对于配置器的原理及一级配置器的介绍请看博文 这里写链接内容 下来我们直接介绍二级空间配置器 二级空间配置器 我们通过之前的学习 已经知道 如果所要申请的空间大于128字节 则直接交至一级空间配置器处理 如果小于128字节 则使用二级空间
  • 如何快速提高英飞凌单片机编译器 TASKING TriCore Eclipse IDE 编译速度

    1 前言 使用英飞凌单片机编译器 TASKING TriCore Eclipse IDE 开发编译时 想必感受最深刻的就是编译速度 那是非常慢了 如果是部分修改的源文件编译还好 不用等太久 而如果选择需要全部编译 那么这个时间就很长了 网上
  • Redis之父:我可不止是一只码农

    一年前我暂停了写代码后开始尝试写科幻小说时 以为这是两条完全不一样的路子 随着写的文字越来越多 不断的推倒重写那也是家常便饭了 我现在总算非常确定了 撸一个大系统和写一本小说本质上其实差不太多 它们之间那是非常相似的 这里我们允许 Anti
  • 敏捷25年:历史阶段与中坚力量

    本文管中窥豹 多有阙疑 但表达的脉络依然有价值 敏捷25年的历史阶段 若龙在渊 1993 2001 1993年 作为XP土壤的C3项目开始 同年 Scrum诞生 这一阶段是新方法论的探索阶段 不满现状的先驱们八仙过海 好比是某组织成立前的各
  • Spring framework 笔记

    文章目录 环境搭建 创建工程 添加maven依赖 快速开始 Spring控制反转 IOC 什么是SpringIOC 配置元数据 Xml Or Annotation XML配置方式 在一个配置文件中导入其他配置文件 Annotation配置方
  • Long和Integer相互转换

    目录 一 int和long互相转换 一 long转化为int 1 类型强制转换 2 利用BigDecimal强制转换 二 int转化为long 1 类型强制转换 2 利用BigDecimal强制转换 二 Long和Integer的互相转换
  • DAP数仓模型及数据集成过程说明

    科技飞速发展的时代 企业信息化建设会越来越完善 越来越体系化 当今数据时代背景下更加强调 重视数据的价值 以数据说话 通过数据为企业提升渠道转化率 改善企业产品 实现精准运营 为企业打造自助模式的数据分析成果 以数据驱动决策 数据分析 无论
  • Wordpress使用CloudFlare的CDN来加速网站(页面规则缓存设置教程

    wordpress博客使用CloudFlare的CDN来加速网站 页面规则缓存设置教程 此篇文章只讲wordpress站点使用CloudFlare CDN的页面规则教程 其他问题可在下方留言 我会一一回复 CloudFlare的CDN有一个
  • java使用itext生成pdf

    效果 maven依赖
  • 2023华为OD机试真题 Java【分割数组的最大差值】

    前言 本题使用Java解答 如果需要Python代码 请参考以下链接 点我 题目内容 我们现在有一个数组nums 需要对该数组进行分割 分割点可以是数组中的任何位置 将该数组分割成两个非空子数组 分别对子数组求和得到两个值 然后需要计算这两
  • python数值计算库教程,Scipy教程 - python数值计算库

    Introduction to Scipy SciPy函数库在NumPy库的基础上增加了众多的数学 科学以及工程计算中常用的库函数 例如线性代数 常微分方程数值求解 信号处理 图像处理 稀疏矩阵等等 涉及的领域众多 可以进行插值处理 信号滤
  • windows 版本 —— nvm-node版本控制

    一 nvm介绍及应用场景 nvm就是nodejs version manage 叫做nodejs 版本管理 而nodejs有很多版本 场景如下 1 而你手上开发的有多个项目又分别是不同的nodejs版本 咱们就可以用nvm轻松切换 2 假设
  • Qt:解决跨线程调用socket/IO类,导致报错的问题(socket notifiers cannot be enabled from another thread)

    Qt 解决跨线程调用socket IO类 导致报错的问题 socket notifiers cannot be enabled from another thread qt 异步线程调用io Jason188080501的博客 CSDN博客
  • 什么是伪共享(false sharing)

    今天看go的sync pool的代码 发现了一个比较陌生的名词 false sharing 之前没听说过 就去查了下 瞬间学到了 type poolLocal struct poolLocalInternal Prevents false