重排序的概念
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段
重排序类型
编译器重排序
编译器会对高级语言进行分析,当编译器认为你的代码是可以优化的时候,会对你的代码进行重新排序。为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,以此保证单线程内程序的执行结果不被改变。
- 优点
更好的使用寄存器以及现代处理器的流水线技术,减少汇编指令的数量,降低程序执行需要的CPU周期,减少CPU读写主存的时间 - 缺点
单纯的编译器重排序并不能保证在多线程的情况下,对于共享变量的可见性的问题。
数据依赖性
int a=3,b;
b=a;
则b的正确结果建立在a数据赋值之后,则称其存在数据依赖关系
控制依赖性
if(flag){
a=a+1;
}
a的运算建立在flag的判断之下(与其赋值无关),则称其存在控制依赖关系。
指令级并行重排序
从指令的角度来说,一条指令的执行时分为多个步骤来进行的,而每一个步骤所需要占用的系统资源是不一样的。而并行重排序的意义就是在某个系统资源释放的时候,能够迅速补上下一条指令的操作,达到提高系统资源利用率的目的。
举例:
比如一条指令的执行分为以下几个步骤:(从上至下,依次归为ABCDE)
- 取指 IF
- 译码和取寄存器操作数 ID
- 执行或者有效地址计算 EX
- 存储器访问 MEM
- 写回寄存器堆 WB
如果不采用并行策略。不同的步骤占用的系统资源不一样,所以当A执行完后,可以补上下一条指令的A操作。后面相同,则可实现指令的流水线生产
采用并行策略后,效率就更快了
但上述最优的情况,存在指令流水线不中断的情况下。
实际情况中,指令并不是一次性按上述5个步骤执行完的,可能存在依赖关系,那么就会发生等待。而一旦按顺序进行指令并行操作,那么就容易发生流水中断,而导致需要时间重新恢复流水生产。
所以就需要采用指令重排序,将合适的指令排序到合适的位置,避免因依赖关系产生等待。
内存系统重排序
定义:
因为我们的CPU处理速度和内存的读写速度不是一个量级的,所以在CPU和内存之间加入的缓存和缓存协议。但是这并不能保证,缓存和内存中的数据一定是一致的。这导致在某一个时间点,各CPU所看到同一内存地址的数据的值可能是不一致的。也即发生了内存系统重排序的现象。
内存屏障
内存屏障(Memory Barrier)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
Load 装载执行
Store 刷到内存
屏障类型 | 指令示例 | 说明解释 |
---|---|---|
LoadLoadBarriers | Load1; LoadLoad; Load2 | 保证Load1全部发生在Load2及其以后操作之前 |
StoreStoreBarriers | Store1; LoadLoad; Store2 | 保证Store1全部发生在Store2及其以后操作之前 |
LoadStoreBarriers | Load1; LoadLoad; Store2 | 保证Load1全部发生在Store2及其以后操作之前 |
StoreLoadBarriers | Store1; LoadLoad; Load2 | 保证Store1全部发生在Load2及其以后操作之前 |
Happans-Before
用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系 。
两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见 。