什么是并行指令集?
在处理器内核中一般会有多个执行单元,比如算术逻辑单元、位移单元等。
- 在引入并行指令集之前,CPU在每个时钟周期内只能执行单条指令,也就是说只有一个执行单元在工作,其他执行单元处于空闲状态;
- 在引入并行指令集之后,CPU在一个时钟周期内可以同时分配多条指令在不同的执行单元中执行。
那么什么是并行指令集的重排序呢?
如下图所示,假设某一段程序有多条指令,不同指令的执行实现也不同。
对于一条从内存中读取数据的指令,CPU的某个执行单元在执行这条指令并等到返回结果之前,按照CPU的执行速度来说它足够处理几百条其他指令,而CPU为了提高执行效率,会根据单元电路的空闲状态和指令能否提前执行的情况进行分析,把那些指令地址顺序靠后的指令提前到读取内存指令之前完成。
实际上,这种优化的本质是通过提前执行其他可执行指令来填补CPU的时间空隙,然后在结束时重新排序运算结果,从而实现指令顺序执行的运行结果。
as-if-serial语义
as-if-serial表示所有的程序指令都可以因为优化而被重排序,但是在优化的过程中必须要保证是在单线程环境下,重排序之后的运行结果和程序代码本身预期的执行结果一致,Java编译器、CPU指令重排序都需要保证在单线程环境下的as-if-serial语义是正确的。
可能有些读者会有疑惑,既然能够保证在单线程环境下的顺序性,那为什么还会存在指令重排序呢?在JSR-133规范中,原文是这么说的。
The compiler, runtime, and hardware are supposed to conspire to create the illusion of as-if-serial semantics, which means that in a single-threaded program, the program should not be able to observe the effects of reorderings.However, reorderings can come into play in incorrectly synchronized multithreaded programs, where one thread is able to observe the effects of other threads, and may be able to detect that variable accesses become visible to other threads in a different order than executed or specified in the program.
编译器、运行时和硬件应该合力创造as-if-serial语义的错觉,这意味着在单线程程序中,程序不应该能够观察到重新排序的效果。然而,重新排序可以 在不正确同步的多线程程序中发挥作用,其中一个线程能够观察其他线程的影响,并且可能能够检测到变量访问对其他线程以与程序中执行或指定的顺序不同的顺序变得可见。
as-if-serial语义允许重排序,CPU层面的指令优化依然存在。在单线程中,这些优化并不会影响整体的执行结果,在多线程中,重排序会带来可见性问题。
另外,为了保证as-if-serial语义是正确的,编译器和处理器不会对存在依赖关系的操作进行指令重排序,因为这样会影响程序的执行结果。我们来看下面这段代码:
public void execute(){
int x=10; //1
int y=5; //2
int c=x+y; //3
}
上述代码按照正常的执行顺序应该是1、2、3,在多线程环境下,可能会出现2、1、3这样的执行顺序,但是一定不会出现3、2、1这样的顺序,因为3与1和2存在数据依赖关系,一旦重排序,就无法保证as-if-serial语义是正确的。