CPU指令重排

CPU指令重排是指CPU为了提高指令执行效率,可能会对指令的执行顺序进行优化,使得(单线程下)指令的实际执行顺序与代码中的顺序不同,但结果是一致的。
这种优化是通过乱序执行和缓存读写重排来实现的。

乱序执行指的是CPU可以在不影响最终结果的前提下,通过并行执行、延迟执行等方式改变指令的执行顺序,从而提高执行效率。
而缓存读写重排指的是CPU会先进行缓存读写操作,而不是直接对内存进行读写,这也会导致多个CPU缓存的可见性问题

CPU是先操作自己的缓存,然后更新到内存中,其他CPU在从内存中获取更新的数据,更新到自己的缓存中,由于内存是隔一段时间刷新一次从缓存中获取最新数据而不是实时的,所以产生缓存一致性问题,也就是CPU自己的缓存对其他CPU不可见。这就是CPU缓存可见性问题。

CPU指令重排可能会导致多线程程序出现一些难以排查和修复的问题,例如线程安全问题和死锁等。为了避免这种问题的发生,可以使用volatile关键字来禁止指令重排。volatile关键字可以保证指令的有序执行,从而避免多线程程序中的可见性问题。

原文链接:https://blog.csdn.net/weixin_46906359/article/details/129781463

内存访问重排序(缓存读写重排)与内存可见性的由来

计算机系统中,为了尽可能地避免处理器访问主内存的时间开销,处理器大多会利用缓存(cache)以提高性能。
在这种模型下会存在一个现象,即缓存中的数据与主内存的数据并不是实时同步的,各CPU(或CPU核心)间缓存的数据也不是实时同步的。这导致在同一个时间点,各CPU所看到同一内存地址的数据的值可能是不一致的。从程序的视角来看,就是在同一个时间点,各个线程所看到的共享变量的值可能是不一致的。

有的观点会将这种现象也视为重排序的一种,命名为“内存系统重排序”。因为这种内存可见性问题造成的结果就好像是内存访问指令发生了重排序一样。

如何解决CPU指令重排 缓存可见性 问题?

CPU提供了两个内存屏障指令(Memory Barrier)用于解决上述两个问题:

写内存屏障(写屏障)
在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其它线程可见。强制写入主内幕才能,这种显示调用,CPU就不会因为性能考虑而去对指令重排。

读内存屏障(读屏障)
在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存中加载,让CPU与主内存保持一致,避免了缓存导致的一致性问题。

volatile通过什么方式禁止指令重排序?

Volatile通过内存屏障可以禁止指令重排序,内存屏障是一个CPU的指令,它可以保证特定操作的执行顺序。
内存屏障分为四种:
StoreStore屏障、StoreLoad屏障、LoadLoad屏障、LoadStore屏障。
JMM针对编译器制定了Volatile重排序的规则。

光看这些理论可能不容易懂,下面我就用通俗的话语来解释一下:
首先是对四种内存屏障的理解,Store相当于是写屏障,Load相当于是读屏障。
比如有两行代码,a=1;x=b;并且我把 a 和 b 修饰为 volatile。
执行 a=1 时,它相当于执行了一次 volatile 写操作;
执行 x=b 时,它相当于先执行 volatile 读取 b,再执行普通写 x 等于 b;
因此在这两行命令之间,就会插入一个 StoreLoad 屏障(前面是写后面也是写),这就是内存屏障。
第一个操作是 volatile 写操作,第二个操作是 volatile 读操作,那么规则中对应的值就是 NO,禁止重排序。这就是 Volatile 禁止指令重排序的原理。
现在,只需要把上面代码的 a 和 b 用 volatile 修饰,就不会发生指令重排序了。

链接:https://juejin.cn/post/6901283327160877063
来源:稀土掘金

内存访问重排序与Java内存模型JMM

Java的目标是成为一门平台无关性的语言,即Write once, run anywhere. 但是不同硬件环境下指令重排序的规则不尽相同。例如,x86下运行正常的Java程序在IA64下就可能得到非预期的运行结果。为此,JSR-1337制定了Java内存模型(Java Memory Model, JMM),旨在提供一个统一的可参考的规范,屏蔽平台差异性。从Java 5开始,Java内存模型成为Java语言规范的一部分。

根据Java内存模型中的规定,可以总结出以下几条happens-before规则。Happens-before的前后两个操作不会被重排序且后者对前者的内存可见。
程序次序法则:线程中的每个动作A都happens-before于该线程中的每一个动作B,其中,在程序中,所有的动作B都能出现在A之后。
监视器锁法则:对一个监视器锁的解锁 happens-before于每一个后续对同一监视器锁的加锁。
volatile变量法则:对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作。
线程启动法则:在一个线程里,对Thread.start的调用会happens-before于每个启动线程的动作。
线程终结法则:线程中的任何动作都happens-before于其他线程检测到这个线程已经终结、或者从Thread.join调用中成功返回,或Thread.isAlive返回false。
中断法则:一个线程调用另一个线程的interrupt happens-before于被中断的线程发现中断。
终结法则:一个对象的构造函数的结束happens-before于这个对象finalizer的开始。
传递性:如果A happens-before于B,且B happens-before于C,则A happens-before于C
Happens-before关系只是对Java内存模型的一种近似性的描述,它并不够严谨,但便于日常程序开发参考使用,关于更严谨的Java内存模型的定义和描述,请阅读JSR-133原文或Java语言规范章节17.4。

除此之外,Java内存模型对volatile和final的语义做了扩展。对volatile语义的扩展保证了volatile变量在一些情况下不会重排序,volatile的64位变量double和long的读取和赋值操作都是原子的。对final语义的扩展保证一个对象的构建方法结束前,所有final成员变量都必须完成初始化(的前提是没有this引用溢出)。

Java内存模型关于重排序的规定,总结后如下表所示:
在这里插入图片描述
表中“第二项操作”的含义是指,第一项操作之后的所有指定操作。如,普通读不能与其之后的所有volatile写重排序。另外,JMM也规定了上述volatile和同步块的规则尽适用于存在多线程访问的情景。例如,若编译器(这里的编译器也包括JIT,下同)证明了一个volatile变量只能被单线程访问,那么就可能会把它做为普通变量来处理。

留白的单元格代表允许在不违反Java基本语义的情况下重排序。例如,编译器不会对对同一内存地址的读和写操作重排序,但是允许对不同地址的读和写操作重排序。

除此之外,为了保证final的新增语义。JSR-133对于final变量的重排序也做了限制。

构建方法内部的final成员变量的存储,并且,假如final成员变量本身是一个引用的话,这个final成员变量可以引用到的一切存储操作,都不能与构建方法外的将当期构建对象赋值于多线程共享变量的存储操作重排序。例如对于如下语句:
x.finalField = v; … ;构建方法边界sharedRef = x; v.afield = 1; x.finalField = v; … ; 构建方法边界sharedRef = x;

这两条语句中,构建方法边界前后的指令都不能重排序。

初始读取共享对象与初始读取该共享对象的final成员变量之间不能重排序。例如对于如下语句:
x = sharedRef; … ; i = x.finalField;

前后两句语句之间不会发生重排序。由于这两句语句有数据依赖关系,编译器本身就不会对它们重排序,但确实有一些处理器会对这种情况重排序,因此特别制定了这一规则。

编译器 指令重排序操作,即编译器生成的机器指令与代码指令顺序不一致

as-if-serial语义

As-if-serial语义的意思是,所有的动作(Action)5都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。 比如,为了保证这一语义,重排序不会发生在有数据依赖的操作之中。

int a = 1;
int b = 2;
int c = a + b;
将上面的代码编译成Java字节码或生成机器指令,可视为展开成了以下几步动作(实际可能会省略或添加某些步骤)。

对a赋值1
对b赋值2
取a的值
取b的值
将取到两个值相加后存入c
在上面5个动作中,动作1可能会和动作2、4重排序,动作2可能会和动作1、3重排序,动作3可能会和动作2、4重排序,动作4可能会和1、3重排序。但动作1和动作3、5不能重排序。动作2和动作4、5不能重排序。因为它们之间存在数据依赖关系,一旦重排,as-if-serial语义便无法保证。

为保证as-if-serial语义,Java异常处理机制也会为重排序做一些特殊处理。例如在下面的代码中,y = 0 / 0可能会被重排序在x = 2之前执行,为了保证最终不致于输出x = 1的错误结果,JIT在重排序时会在catch语句中插入错误代偿代码,将x赋值为2,将程序恢复到发生异常时应有的状态。这种做法的确将异常捕捉的逻辑变得复杂了,但是JIT的优化的原则是,尽力优化正常运行下的代码逻辑,哪怕以catch块逻辑变得复杂为代价,毕竟,进入catch块内是一种“异常”情况的表现。6

public class Reordering {
public static void main(String[] args) {
int x, y;
x = 1;
try {
x = 2;
y = 0 / 0;
} catch (Exception e) {
} finally {
System.out.println("x = " + x);
}
}
}

https://tech.meituan.com/2014/09/23/java-memory-reordering.html

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

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

相关文章

卡车手机远程启动一键启动无钥匙进入有哪些好处

随着汽车科技的发展,卡车智能化升级已成为趋势,其中手机控车、远程启动、无钥匙进入及一键启动等功能显著提升了驾驶便捷性与安全性。以下从功能特点、技术原理、适用场景及改装建议等方面展开说明。一、核心功能及技术特点1. 无钥匙进入系统自动感应操作…

【pyqt5】SP_(Standard Pixmap)的标准图标常量及其对应的图标

目录 **常见SP_图标分类及用途** **1. 箭头和导航图标** **2. 文件和编辑操作** **3. 系统状态和通知** **4. 应用程序和菜单** **5. 数据视图控件** **完整列表(部分)** **使用建议** **6. 数据操作图标** **7. 编辑和文本操作** **8. 媒体控制图标** **9. 系统和应用状态**…

VS Git巨坑合并分支失败导致多项无关改变

基于主分支创建的临时分支上进行了一些开发,合并回主分支,期间主分支没有进行任何更改还是创建临时分支时的状态,但合并莫名其妙报错 “1 uncommitted …”,我可以确认主分支和临时分支均没有尚未提交的更改。更恶心的是&#xff…

开始记录U9客开过程中听点滴

很久没有更新了。终于有时间可以拾起U9的研究当中。时间长了就生疏了很多,记录下来备查吧。用这个工具可以生成一个VS 2022的项目,在指定的地方写自已的代码既可。BE插件,Busing Plugin 商业插件。总结一下,BE插件是应用于某一个单…

C# 异步编程(使用异步Lambda表达式)

使用异步Lambda表达式 到目前为止,本章只介绍了异步方法。但我们曾经说过,你还可以使用异步匿名方法和异步 Lambda表达式。这些构造尤其适合那些只有少量工作要做的事件处理程序。下面的代码片段将 一个表达式注册为一个按钮点击事件的事件处理程序。 st…

K8S云原生监控方案Prometheus+grafana

目录 1. 概述 1.1 系统架构 1.1.1 架构图 ​编辑 1.2 环境准备 2. 部署prometheus 2.1 创建Namespace 2.2 创建ConfigMap资源 2.3 创建ServiceAccount,Clusterrole,Clusterrolebinding,Service,Deployment,in…

Matplotlib库:Python数据可视化的基石,发现它的美

Matplotlib是Python中最基础、最广泛使用的数据可视化库,它提供了类似MATLAB的绘图接口,能够创建高质量的静态、动态和交互式图表。作为科学计算和数据可视化的核心工具,Matplotlib几乎成为Python数据科学生态系统的标准可视化组件。 今天与…

每日算法刷题Day59:8.9:leetcode 队列8道题,用时2h30min

一、基础 1.套路 1.队列常用在 BFS 中&#xff0c;见 网格图题单 和 图论题单。 2.队列(queue)是容器适配器&#xff0c;功能较少。 队尾插入元素&#xff0c;队首弹出元素&#xff0c;可以访问队首元素、队尾元素和队列长度。 无begin(),end()等迭代器 queue<int> qu…

Java选手如何看待Golang

写在前面&#xff1a;翻了很多博客&#xff0c;一直没有Java选手转行golang的学习经验贴&#xff0c;思考很久&#xff0c;写下这篇Java选手怎么看待golang这个冉冉新星。1.走完所有golang基础之后的感受&#xff08;1&#xff09;最大的不适应有这么几点&#xff1a;---变量定…

Codeforces Round 967 (Div. 2) D. Longest Max Min Subsequence

假设我们要选a[j]为答案数组b[i]&#xff0c;从i从1~m&#xff08;m为a数组中不同数的个数&#xff09;建立一个suf数组&#xff0c;代表以i开头的后缀有多少个不同且在b[1~i-1]中未出现过的的个数&#xff0c;预处理suf&#xff0c;发现后续我们怎么选数改变suf&#xff0c;su…

Linux运维新手的修炼手扎之第27天

mysql服务1 主从复制集群&#xff1a;多主机集群【复制】负载过大解决方案&#xff1a;横向扩展[增加服务器节点分散负载]、纵向扩展[提升单机硬件性能]复制工作原理&#xff1a;前提&#xff1a;基础数据一样&#xff0c;主节点上有同步数据用的账号主角色【二进制日志、binlo…

【Linux】Linux增删改查命令大全(附频率评级)

Linux增删改查命令大全&#xff08;附频率评级&#xff09;* 《Linux命令全景手册&#xff1a;增删改查全场景解析&#xff08;含136个高频命令&#xff09;》 按使用频率★分级 | 测试/运维/开发均适用 | 附思维导图下载一、命令全景表&#xff08;增删改查频率评级&#xff0…

SwiftUI 登录页面键盘约束冲突与卡顿优化全攻略

网罗开发&#xff08;小红书、快手、视频号同名&#xff09;大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等方…

建筑物实例分割数据集-9,700 张图片 城市规划与发展 灾害评估与应急响应 房地产市场分析 智慧城市管理 地理信息系统(GIS) 环境影响评估

建筑物实例分割数据集-9,700 张图片&#x1f4e6; 已发布目标检测数据集合集&#xff08;持续更新&#xff09;&#x1f3e2; 建筑物实例分割数据集介绍&#x1f4cc; 数据集概览包含类别&#x1f3af; 应用场景&#x1f5bc; 数据样本展示使用建议&#x1f31f; 数据集特色&am…

LeetCode 刷题【36. 有效的数独】

36. 有效的数独 自己做 解&#xff1a;多层for class Solution { public:bool isValidSudoku(vector<vector<char>>& board) {int hight board.size(); //长if (hight 0)return true;int wide board[0].size(); //宽//判断一行是否出现重复bool…

Java 日志从入门到精通:告别日志混乱

作为一名 Java 开发者&#xff0c;你是否曾在生产环境故障排查时面对过这样的困境&#xff1a;系统报错却找不到关键日志&#xff0c;日志文件大到无法打开&#xff0c;或者日志内容杂乱无章根本无法定位问题&#xff1f;日志作为系统运行的 “黑匣子”&#xff0c;其重要性不言…

系统开发 Day1

前端开发 目的&#xff1a; 开发一个平台&#xff08;网站&#xff09; - 前端开发&#xff1a;HTML CSS JavaScript - web框架&#xff1a;接受请求和处理 - MySQL数据库&#xff1a;存储数据的地方快速上手&#xff1a;基于Flask Web框架快速搭建一个网站 深度学习&#xff…

机器视觉任务(目标检测、实例分割、姿态估计、多目标跟踪、单目标跟踪、图像分类、单目深度估计)常用算法及公开数据集分享

本文按目标检测、实例分割、姿态估计、多目标跟踪、单目标跟踪、图像分类、单目深度估计七个任务分类&#xff0c;融合数据集介绍、评价指标及推荐算法&#xff0c;方便查阅&#xff1a; 一、目标检测 目标检测任务需定位图像中目标的边界框&#xff08;bounding box&#xff0…

MongoTemplate中setOnInsert与set方法的深度解析

MongoTemplate中setOnInsert与set方法的深度解析 在Spring Data MongoDB的MongoTemplate中&#xff0c;setOnInsert和set方法都是在更新文档时使用的&#xff0c;但它们在处理upsert操作&#xff08;即&#xff0c;如果文档不存在则插入&#xff0c;存在则更新&#xff09;时扮…

利用OJ判题的多语言优雅解耦方法深入体会模板方法模式、策略模式、工厂模式的妙用

在线评测系统&#xff08;Online Judge, OJ&#xff09;的核心是判题引擎&#xff0c;其关键挑战在于如何高效、安全且可扩展地支持多种编程语言。在博主的项目练习过程中&#xff0c;借鉴了相关设计模式实现一种架构设计方案&#xff0c;即通过组合运用模板方法、策略、工厂等…