java八股文面试[多线程]——指令重排序

news2024/11/22 20:53:03

关于a的操作,由原来的6个指令,变成了4个指令。

1. 指令重排序的介绍
1)指令重排序的类型
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。

重排序分三种类型:
编译器优化的重排序 编译器在不改变单线程程序语义的前提下(代码中不包含synchronized关键字),可以重新安排语句的执行顺序。
指令级并行的重排序 现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
内存系统的重排序 由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行


重排序又可以分为两大类

一是编译器重排序:编译器优化重排序,

二是处理器重排序:指令级并行重排序、内存系统重排序。


2)重排序的流程
从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:

在这里插入图片描述


3)重排序意义
处理器为提高运算速度而做出违背代码原有顺序的优化


4)存在的问题
这三种重排序方法都可能会导致多线程程序出现内存可见性问题

2. 内存系统重排序
1)内存系统的读操作
为提高从内存读取的效率,对读操作进行优化,将其称为程序乱序执行优化。CPU为了提高指令执行效率,会在一条指令执行过程中(比如去内存读数据(慢100倍)),去同时执行另一条指令,前提是,两条指令必须是独立的、没有依赖关系,cpu一般是依赖指令间的内存引用关系来判断的指令间的”独立关系”。


2)内存系统的写操作
写操作的优化称为合并写入技术。现代的处理器使用写缓冲区来临时保存向内存写入的数据。
写操作优化的过程:乱序当cpu执行存储指令时,它会首先试图将数据写到离cpu最近的L1_cache, 如果此时cpu出现L1未命中,则会访问下一级缓存。速度上L1_cache基本能和cpu持平,其他的均明显低于cpu,L2_cache的速度大约比cpu慢20-30倍,而且还存在L2_cache不命中的情况,又需要更多的周期去主存读取。其实在L1_cache未命中以后,cpu就会使用一个另外的缓冲区,叫做合并写存储缓冲区。这一技术称为合并写入技术。


在请求L2_cache缓存行的所有权尚未完成时,cpu会把待写入的数据写入到合并写存储缓冲区,该缓冲区大小和一个cache line大小,一般都是64字节。这个缓冲区允许cpu在写入或者读取该缓冲区数据的同时继续执行其他指令,这就缓解了cpu写数据时cache miss时的性能影响。当后续的写操作需要修改相同的缓存行时,在将后续的写操作提交到L2缓存之前,可以进行缓冲区写合并。

这些64字节的缓冲区维护了一个64位的字段,每更新一个字节就会设置对应的位,来表示将缓冲区交换到外部缓存时哪些数据是有效的。当然,如果程序读取已被写入到该缓冲区的某些数据,那么在读取缓存数据之前会先去读取本缓冲区的。经过上述步骤后,缓冲区的数据还是会在某个延时的时刻更新到外部的缓存(L2_cache).如果能在缓冲区传输到缓存之前将其尽可能填满,这样的效果就会提高各级传输总线的效率,以提高程序性能。


3)写缓冲区造成的优势
优势1,合并写缓冲区中对同一内存地址的多次写操作,可以减少对内存总线的占用;

优势2,通过以批处理的方式刷新写缓冲区;

优势3,写缓冲区可以保证指令流水线持续运行,避免由于处理器停顿下来等待向内存写入数据而产生的延迟。


4)出现问题的原因
单核时代处理器做出的优化可以保证执行结果不会远离预期目标,但是,在多核时代却并非如此。在多核时代,同时会有多个核同时执行指令,每一个核的指令都可能被乱序。另外,处理器还引入了L1,L2,…,Ln等多级缓存机制,每个核心都有自己的缓存机制,这样就导致了逻辑次序上后写入内存的数据未必真的最后写入。最后就带来一个问题,如果不做任何防护措施,处理器最终得出的结果和逻辑得出结果会大不相同。
比如,在一个核上执行写入操作,并在最后写一个标记用来表示操作完毕,之后从另外一个核上通过判断这个标记来判定所需要的数据是否已经就绪,这种做法就存在一定风险:标记位先被写入但之前的操作却并未完成(可能是未计算完成,也可能是数据没有从处理器缓存刷新到主存中,最终导致另外的核使用了错误的数据)。

3. 内存屏障
3.1. 硬件内存屏障
1)CPU内存屏障(硬件内存屏障)
功能:为了解决代码的乱序执行问题,在CPU级别上引入了内存屏障,这里跟JAVA的内存屏障不是一个问题
分类:
指令 功能
sfence 在sfence指令前的写操作必须在sfence指令后的写操作之前完成
lfence 在lfence指令前的读操作必须在lfence指令后的读操作之前完成
mfence 在mfence指令前的读写操作必须在mfence指令后的读写操作之前完成
原子指令:例如X86中的lock指令,执行时会锁住内存子系统来保障执行顺序不改变,甚至能跨多个CPU。
Software Locks通常使用内存屏障原子指令来实现变量的可见性和执行顺序不变

3.2. 内存系统的内存屏障
1)使用背景
对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM的处理器重排序规则会要求java编译器在生成指令序列时,插入特定类型的内存屏障(memory barriers,intel称之为memory fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序(不是所有的处理器重排序都要禁止)。


2)基本使用
内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。

JMM把内存屏障指令分为下列四类:
LoadLoad屏障 语句:Load1; LoadLoad; Load2
在Load2及后续读取操作要在读取的数据被访问前,保证Load1要读取的数据被读取完毕
StoreStore屏障 语句:Store1; StoreStore; Store2
在Store2及后续写入操作执行前,保证Store1的写入操作对其他处理器可见
LoadStore屏障 语句:Load1; LoadStore; Store2
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕
StoreLoad屏障 语句:Store1; StoreLoad; Load2
在Load2及后续所有读取操作执行前,保证Store1的写入对其他处理器是可见

3)StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他三个屏障的效果。现代的多处理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(buffer fully flush)


4)允许指令重排序的列表
下面是常见处理器允许的重排序类型的列表:

在这里插入图片描述
注:上表单元格中的“N”表示处理器不允许两个操作重排序,“Y”表示允许重排序。
从上表我们可以看出:常见的处理器都允许Store-Load重排序;常见的处理器都不允许对存在数据依赖的操作做重排序。sparc-TSO和x86拥有相对较强的处理器内存模型,它们仅允许对写-读操作做重排序(因为它们都使用了写缓冲区)。

4. happens-before关系
1)基本介绍
JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间


2)happens-before规则
与程序员密切相关的happens-before规则如下:
程序顺序规则 一个线程中的每个操作,happens- before 于该线程中的任意后续操作
监视器锁规则 对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁
volatile变量规则 对一个volatile域的写,happens- before 于任意后续对这个volatile域的读
传递性 如果A happens- before B,且B happens- before C,那么A happens- before C
Start规则
Join规则
特殊说明:两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。


3)happens-before与JMM的关系

在这里插入图片描述
如上图所示,一个happens-before规则通常对应于多个编译器处理器重排序规则。对于java程序员来说,happens-before规则简单易懂,避免java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现。

3.5. as-if-serial语义
1)数据依赖性
如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分下列三种类型:

在这里插入图片描述
上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变
注:这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。


2)as-if-serial语义
不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变,编译器,runtime 和处理器都必须遵守as-if-serial语义。即编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。
as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

 

 参考:

【23版面试突击】你知道什么是指令重排序?为什么要重排序?_哔哩哔哩_bilibili

指令重排序_Waiting_Mr_Liu的博客-CSDN博客

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

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

相关文章

深度分析:如何轻松掌握文件大小管理

大家好,今天我要与大家分享一个实用至极的脚本。简单易用,但效果却让人惊艳。它可以在几秒钟内完成文件大小的统计,并生成一份统计信息。 功能概览 完整性分析:一次性告诉你不同大小区间的文件分布,让你一目了然。 速…

【Unity】常见的角色移动旋转

在Unity 3D游戏引擎中,可以使用不同的方式对物体进行旋转。以下是几种常见的旋转方式: 欧拉角(Euler Angles):欧拉角是一种常用的旋转表示方法,通过绕物体的 X、Y 和 Z 轴的旋转角度来描述物体的旋转。在Un…

七、MySQL(DML)如何往表中添加数据?

1、基础语法: (1)一对一添加数据: 一次只能添加一组数据,可以指定对应字段 insert into 表名 (字段名1,字段名2,……) values (数值1.数值2,……) (2)给全部字段添加数据&#…

vmware安装centos7虚拟机图文详解

1.打开vmware,点击创建新的虚拟机 2.选择自定义(高级),下一步 3.下一步 4.选择稍后安装操作系统 5.操作系统选择linux,版本选择centos7 64位,下一步 6.创建文件夹,选择虚拟机存放的位置&#x…

Exactly Protocol 攻击事件原理分析

Exactly Protocol 攻击事件原理分析 8 月 18 日,Exactly protocol 遭遇黑客攻击,攻击者已获利约 1204 万美元。 安全公司Safful对此事件第一时间进行了技术分析,并总结了安全防范手段,希望后续项目可以引以为戒,共筑区…

企业小件固定资产怎么管理,有什么有效方式?

小件固定资产是指价值较低、使用寿命较短的固定资产,一般用于生产、制造、维修等领域。一些常见的行业中广泛使用小件固定资产:  制造业:小件固定资产在制造业中应用广泛,如机器设备、工具、测量仪器等。  建筑业:…

Spring Boot业务系统如何实现海量数据高效实时搜索

1.概述 我们都知道随着业务系统的发展和使用,数据库存储的业务数据量会越来越大,逐渐成为了业务系统的瓶颈。在阿里巴巴开发手册中也建议:单表行数超过500万行或者单表容量超过2GB才推荐进行分库分表,如果预计三年后数据量根本达…

科创板50etf和科创50etf的区别不同点?一定要看!

很多在交易期权的时候会对科创50ETF期权和科创板50ETF期权标的物选择有疑问,两者与有何区别呢,选择哪个比较合适,别着急,下文为大家介绍科创板50etf和科创50etf的区别不同点?一定要看!本文来自:…

谁做的方案,竟把财务数据分析套路都摸透了

来不及计算组合多变又复杂量大的财务数据分析指标?不知道该用哪些财务数据分析方法技巧?不知道怎么把信息量巨大的财务数据分析报表做得易读易理解?别担心,奥威BI数据可视化工具都把套路摸透了,并提出一套系统、标准化…

kafka+Kraft模式集群+安全认证

Kraft模式安全认证 前章内容聊到了Kafka的Kraft集群的配置及使用。本篇再来说说kafka的安全认证方面的配置,。 Kafka提供了多种方式来进行安全认证,包括身份认证、授权和加密传输。一些常用的Kafka安全认证方式: SSL/TLS:使用S…

go的数据结构-变量的大小是0字节

查看变量大小 unsafe.Sizeof(int(0)) 变量: 指针: int和指针大小跟随系统字长,空结构体没有任何成员,是有地址没有长度,所有空结构体都指向同一个结构体地址(不被包含在其他结构体中)zerobas…

OpenHarmony Meetup 2023 广州站圆满举办,城市巡回全面启航

“OpenHarmony正当时——技术开源”OpenHarmony Meetup 2023城市巡回活动,旨在通过meetup线下交流形式,解读OpenHarmony作为下一代智能终端操作系统的新版本及成果转化,提升开发者对OpenHarmony的关注度,普及OpenHarmony开发技能&…

高效利用隧道代理实现无阻塞数据采集

在当今信息时代,大量的有价值数据分散于各个网站和平台。然而,许多网站对爬虫程序进行限制或封禁,使得传统方式下的数据采集变得困难重重。本文将向您介绍如何通过使用隧道代理来解决这一问题,并帮助您成为一名高效、顺畅的数据采…

桌面端后台项目笔记

套用模板 vue-pure-admin 所用主要框架笔记 1. electron app const { app } require(electron) app.on(事件名, () > {} // 回调函数)常用事件 will-finish-launching 当应用程序完成基础的启动的时候触发ready:electron完成初始化时触发window-all-close…

A10的pll reconfig

一、前言 我之前写过《quartus动态配置pll reconfig》,讲的是A5的pll reconfig。 这次调A10,结果发现寄存器配置、地址、操作流程都变了,不能无脑移植。 二、寄存器配置 1,A5的寄存器配置如下: 其中基地址如下&…

皮卡丘靶场搭建遇到的问题大全

该博客记录我在安装皮卡丘靶场中遇到的一些问题。 1、 phpstudy_pro启动Mysql失败 自己电脑开启了mysql服务,使用winr,services.msc,找到自己的mysql服务,关闭。再次尝试使用phpstudy_pro启动mysql,成功解决。 2、皮…

基于Matlab实现生活中的图像信号分类(附上源码+数据集)

在我们的日常生活中,我们经常会遇到各种各样的图像信号,例如照片、视频、图标等等。对这些图像信号进行分类和识别对于我们来说是非常有用的。在本文中,我将介绍如何使用Matlab来实现生活中的图像信号分类。 文章目录 介绍源码数据集下载 介…

ElasticSearch学习5-- 使用RestClient查询文档

1、查询基本步骤 1、创建SearchRequest对象 2、准备Request.source(),也就是DSL。 QueryBuilders来构建查询条件 传入Request.source() 的 query() 方法 3、发送请求,得到结果 4、解析结果(参考JSON结果,从外到内…

美团 Flink 资源调度优化实践

摘要:本文整理自美团数据平台计算引擎组工程师冯斐,在 Flink Forward Asia 2022 生产实践专场的分享。本篇内容主要分为四个部分: 相关背景和问题解决思路分析资源调度优化实践后续规划 点击查看原文视频 & 演讲PPT 一、相关背景和问题 在…

OpenSSL 远程代码执行漏洞 CVE-2022-1292 升级方案及步骤

升级方案 升级步骤 1.查看版本号 命令:openssl version 2.下载安全版本openssl: openssl官网 下载 openssl-1.1.1o.tar.gz 3.编译安装 在/usr/lib/下创建目录openssl 输入命令: mkdir openssl 将下载的包openssl-1.1.1o.tar.gz放到这个目录,并解压 输入命令: tar -xv…