从源代码到Runtime发生的重排序
源代码和Runtime时执行的代码很可能不一样,这是因为编译器、处理器常常会为了追求性能对改变执行顺序。然而改变顺序执行很危险,很有可能使得运行结果和预想的不一样,特别是当重排序共享变量时。
从源代码到Runtime需要经过三步的重排序:
编译器重排序
为了提高性能,在不改变单线程的执行结果下,可以改变语句执行顺序。
比如尽可能的减少寄存器的读写次数,充分利用局部性。像下面这段代码这样,交替的读x、y,会导致寄存器频繁的交替存储x和y,最糟的情况下寄存器要存储3次x和3次y。如果能让x的一系列操作一块做完,y的一块做完,理想情况下寄存器只需要存储1次x和1次y。
//优化前
int x = 1;
int y = 2;
int a1 = x * 1;
int b1 = y * 1;
int a2 = x * 2;
int b2 = y * 2;
int a3 = x * 3;
int b3 = y * 3;
//优化后
int x = 1;
int y = 2;
int a1 = x * 1;
int a2 = x * 2;
int a3 = x * 3;
int b1 = y * 1;
int b2 = y * 2;
int b3 = y * 3;
指令重排序
指令重排序是处理器层面做的优化。处理器在执行时往往会因为一些限制而等待,如访存的地址不在cache中发生miss,这时就需要到内存甚至外存去取,然而内存和外区的读取速度比CPU执行速度慢得多。
早期处理器是顺序执行(in-order execution)的,在内存、外存读取数据这段时间,处理器就一直处于等待状态。现在处理器一般都是乱序执行(out-of-order execution),处理器会在等待数据的时候去执行其他已准备好的操作,不会让处理器一直等待。
满足乱序执行的条件:
- 该缓存的操作数缓存好
- 有空闲的执行单元
对于下面这段汇编代码,操作1如果发生cache miss,则需要等待读取内存外存。看看有没有能优先执行的指令,操作2依赖于操作1,不能被优先执行,操作3不依赖1和2,所以能优先执行操作3。
所以实际执行顺序是3>1>2
LDR R1, [R0];//操作1
ADD R2, R1, R1;//操作2
ADD R3, R4, R4;//操作3
内存系统重排序
由于处理器有读、写缓存区,写缓存区没有及时刷新到内存,造成其他处理器读到的值不是最新的,使得处理器执行的读写操作与内存上反应出的顺序不一致。
如下面这个例子,可能造成处理器A读到的b=0,处理器B读到的a=0。A1写a=1先写到处理器A的写缓存区中,此时内存中a=0。如果这时处理器B从内存中读a,读到的将是0。
以处理器A来说,处理器A执行的顺序是A1>A2>A3,但是由于写缓存区没有及时刷新到内存,所以实际顺序为A2>A1>A3。
初始化:
a = 0;
b = 0;
处理器A执行
a = 1; //A1
read(b); //A2
处理器B执行
b = 2; //B1
read(a); //B2
阻止重排序
不论哪种重排序都可能造成共享变量中线程间不可见,这会改变程序运行结果。所以需要禁止对那些要求可见的共享变量重排序。
- 阻止编译重排序:禁止编译器在某些时候重排序。
- 阻止指令重排序和内存系统重排序:使用内存屏障或Lock前缀指令
从源代码到Runtime发生的重排序的更多相关文章
- Java并发编程原理与实战四十一:重排序 和 happens-before
一.概念理解 首先我们先来了解一下什么是重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段. 从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序,如下图 ...
- 指令重排序及Happens-before法则随笔
指令重排序 对主存的一次访问一般花费硬件的数百次时钟周期.处理器通过缓存(caching)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定内存操作的顺序.也就是说,程序的读写操作不一定会按 ...
- 多线程学习:Volatile与Synchronized的区别、什么是重排序
java线程的内存模型 java的线程内存模型中定义了每个线程都有一份自己的共享变量副本(本地内存),里面存放自己私有的数据,其他线程不能直接访问,而一些共享变量则存在主内存中,供所有线程访问. 上图 ...
- 深入浅出Java并发包—指令重排序
前面大致提到了JDK中的一些个原子类,也提到原子类是并发的基础,更提到所谓的线程安全,其实这些类或者并发包中的这么一些类,都是为了保证系统在运行时是线程安全的,那到底怎么样才算是线程安全呢? Java ...
- JVM并发机制的探讨——内存模型、内存可见性和指令重排序
并发本来就是个有意思的问题,尤其是现在又流行这么一句话:“高帅富加机器,穷矮搓搞优化”. 从这句话可以看到,无论是高帅富还是穷矮搓都需要深入理解并发编程,高帅富加多了机器,需要协调多台机器或者多个CP ...
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ...
- Jvm 中的 重排序、主存、原子操作
一.重排序 好处:重排序可以提升性能,避免在一个耗时很长的指令在“执行”阶段呆很长时间,而导致后续的指令都卡在“执行”之前的阶段上. 坏处:重排序对多线程的影响 class ReorderExampl ...
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则[转]
在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念. 在Java Concurrency in Practice中是这样定义线程安全的: ...
- JMM中的重排序及内存屏障
目录 1. 概述 2. 重排序 2-1. as-if-serial语义 2-2. 重排序的种类 2-3. 从Java源代码到最终实际执行的指令序列, 会分别经历下面3中重排序. 3. 内存屏障类型 3 ...
随机推荐
- css基础语法三
[]伪类选择器] 1.写法: 伪类选择器,在选择器后面,用:分隔,紧接伪类状态: eg : .a:link 2. 超链接的伪类状态: :link - 未访问状态 :visited - 已访问状态 :h ...
- D3.js从入门到“放弃”指南
前言 近期略有点诸事不顺,趁略有闲余之时,玩起D3.js.之前实际项目中主要是用各种chart如hightchart.echarts等,这些图形库玩起来貌都是完美的,一切皆可配置,但几年前接触了D3之 ...
- Android模仿iOS iMessages10照片选择器的实现
不知不觉已经接近半年多没有写过博客了,这段时间,也是我刚好毕业走出校园的时间,由于学习工作的原因,一直没有真正静下心来写下些什么东西.这个星期刚入了小米笔记本pro的坑,本着新电脑新生活的理念嘻嘻-- ...
- undefined 与void 0
参考:https://segmentfault.com/a/1190000000474941 Javascript中void是一个操作符,该操作符指定要计算一个表达式但是不返回值.void 操作符用法 ...
- 认识Java WEB应用
JavaWeb应用概念 在Sun的JavaServlet规范中,对Java Web应用作了这样定义:JAVA Web应用由一组Servlet.HTML页.类.以及其它可以被绑定的资源构造.它可以在各种 ...
- Scrum Meeting Alpha - 10
Scrum Meeting Alpha - 10 NewTeam 2017/11/06 地点:主楼和3号楼之间的走廊2楼 任务反馈 团队成员 完成任务 计划任务 安万贺 完成了对涉及内容修改的API的 ...
- C#连接六类数据库的代码集
本文列出了C#连接Access.SQL Server.Oracle.MySQL.DB2和SyBase六种不同数据库的程序源码和需要注意的点. 1.C#连接Access 程序代码: ;
- C语言的第一个程序 “hello world!”
1,C语言的简介 C语言是一门通用计算机编程语言,应用广泛.C语言的设计目标是提供一种能以简易的方式编译.处理低级存储器.产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言. ...
- jQuery的事件绑定命名空间
jQuery的bind的函数在实际应用中用的不是特别多,只是他可以绑定一个事件,但不会即时触发,也可以通过unbind来解除绑定.在没有看到这篇文章之前,我一直不知道原来bind也可以有命名空间.事实 ...
- fragmentTabHost 使用示例
目前我们看微信的底部,有四个导航栏,那我们应该来怎么实现类似的导航栏呢? 在 android 4.0 的时候,推出了一个新的工具,fragmentTabHost . fragmentTabHost ...