编译期重排序和执行期重排序,分别相应编译时和执行时环境。

不要如果指令运行的顺序,你无法预知不同线程之间的指令会以何种顺序运行。

单线程程序中,通常我们easy如果指令是顺序运行的,否则能够想象程序会发生什么可怕的变化。理想的模型是:各种指令运行的顺序是唯一且有序的,这个顺序就是它们被编写在代码中的顺序。与处理器或其他因素无关,这样的模型被称作顺序一致性模型。也是基于冯·诺依曼体系的模型。

当然,这样的如果本身是合理的,在实践中也鲜有异常发生。但其实,没有哪个现代多处理器架构会採用这样的模型。由于它是在是太低效了。

而在编译优化和CPU流水线中,差点儿都涉及到指令重排序。

编译期重排序

编译期重排序的典型就是通过调整指令顺序。在不改变程序语义的前提下。尽可能降低寄存器的读取、存储次数。充分复用寄存器的存储值。

如果第一条指令计算一个值赋给变量A并存放在寄存器中,第二条指令与A无关但须要占用寄存器(如果它将占用A所在的那个寄存器),第三条指令使用A的值且与第二条指令无关。

那么如果依照顺序一致性模型,A在第一条指令运行过后被放入寄存器,在第二条指令运行时A不再存在。第三条指令运行时A又一次被读入寄存器。而这个过程中,A的值没有发生变化。通常编译器都会交换第二和第三条指令的位置。这样第一条指令结束时A存在于寄存器中,接下来能够直接从寄存器中读取A的值,减少了反复读取的开销。

重排序对于流水线的意义

现代CPU差点儿都採用流水线机制加快指令的处理速度。一般来说,一条指令须要若干个CPU时钟周期处理。而通过流水线并行运行。能够在同等的时钟周期内运行若干条指令。详细做法简单地说就是把指令分为不同的运行周期,比如读取、寻址、解析、运行等步骤。并放在不同的元件中处理。同一时候在运行单元EU中,功能单元被分为不同的元件。比如加法元件、乘法元件、载入元件、存储元件等,能够进一步实现不同的计算并行运行。


流水线架构决定了指令应该被并行运行,而不是在顺序化模型中所觉得的那样。

重排序有利于充分使用流水线,进而达到超标量的效果。


确保顺序性

在单线程环境下。指令运行的终于效果应当与其在顺序运行下的效果一致,否则这样的优化便会失去意义。

通常不管是在编译期还是执行期进行的指令重排序,都会满足上面的原则。


Java存储模型中的重排序

JVM能够对它们进行随意排序以提高程序性能。


volatilekeyword能够保证变量的可见性。由于对volatile的操作都在Main Memory中,而Main Memory是被全部线程所共享的。这里的代价就是牺牲了性能。无法利用寄存器或Cache。由于它们都不是全局的,无法保证可见性。可能产生脏读。


volatile另一个作用就是局部阻止重排序的发生。对volatile变量的操作指令都不会被重排序,由于假设重排序,又可能产生可见性问题。

在保证可见性方面,锁(包含显式锁、对象锁)以及对原子变量的读写都能够确保变量的可见性。可是实现方式略有不同。比如同步锁保证得到锁时从内存里又一次读入数据刷新缓存,释放锁时将数据写回内存以保数据可见。而volatile变量干脆都是读写内存。


编译期重排序和执行期重排序,分别相应编译时和执行时环境。

不要如果指令运行的顺序,你无法预知不同线程之间的指令会以何种顺序运行。

单线程程序中,通常我们easy如果指令是顺序运行的,否则能够想象程序会发生什么可怕的变化。理想的模型是:各种指令运行的顺序是唯一且有序的,这个顺序就是它们被编写在代码中的顺序。与处理器或其他因素无关,这样的模型被称作顺序一致性模型。也是基于冯·诺依曼体系的模型。

当然,这样的如果本身是合理的,在实践中也鲜有异常发生。但其实,没有哪个现代多处理器架构会採用这样的模型。由于它是在是太低效了。

而在编译优化和CPU流水线中,差点儿都涉及到指令重排序。

编译期重排序

编译期重排序的典型就是通过调整指令顺序。在不改变程序语义的前提下。尽可能降低寄存器的读取、存储次数。充分复用寄存器的存储值。

如果第一条指令计算一个值赋给变量A并存放在寄存器中,第二条指令与A无关但须要占用寄存器(如果它将占用A所在的那个寄存器),第三条指令使用A的值且与第二条指令无关。

那么如果依照顺序一致性模型,A在第一条指令运行过后被放入寄存器,在第二条指令运行时A不再存在。第三条指令运行时A又一次被读入寄存器。而这个过程中,A的值没有发生变化。通常编译器都会交换第二和第三条指令的位置。这样第一条指令结束时A存在于寄存器中,接下来能够直接从寄存器中读取A的值,减少了反复读取的开销。

重排序对于流水线的意义

现代CPU差点儿都採用流水线机制加快指令的处理速度。一般来说,一条指令须要若干个CPU时钟周期处理。而通过流水线并行运行。能够在同等的时钟周期内运行若干条指令。详细做法简单地说就是把指令分为不同的运行周期,比如读取、寻址、解析、运行等步骤。并放在不同的元件中处理。同一时候在运行单元EU中,功能单元被分为不同的元件。比如加法元件、乘法元件、载入元件、存储元件等,能够进一步实现不同的计算并行运行。


流水线架构决定了指令应该被并行运行,而不是在顺序化模型中所觉得的那样。

重排序有利于充分使用流水线,进而达到超标量的效果。


确保顺序性

在单线程环境下。指令运行的终于效果应当与其在顺序运行下的效果一致,否则这样的优化便会失去意义。

通常不管是在编译期还是执行期进行的指令重排序,都会满足上面的原则。


Java存储模型中的重排序

JVM能够对它们进行随意排序以提高程序性能。


volatilekeyword能够保证变量的可见性。由于对volatile的操作都在Main Memory中,而Main Memory是被全部线程所共享的。这里的代价就是牺牲了性能。无法利用寄存器或Cache。由于它们都不是全局的,无法保证可见性。可能产生脏读。


volatile另一个作用就是局部阻止重排序的发生。对volatile变量的操作指令都不会被重排序,由于假设重排序,又可能产生可见性问题。

在保证可见性方面,锁(包含显式锁、对象锁)以及对原子变量的读写都能够确保变量的可见性。可是实现方式略有不同。比如同步锁保证得到锁时从内存里又一次读入数据刷新缓存,释放锁时将数据写回内存以保数据可见。而volatile变量干脆都是读写内存。