循序运行
主条目:指令周期
在早期的处理器中,指令的执行一般在以下的步骤中完成:
1 指令获取。
2 如果输入的运算对象是可以获取的(比如已经存在于寄存器中),这条指令会被发送到合适的功能单元。如果一个或者更多的运算对象在当前的时钟周期中是不可获取的(通常需要从主存获取),处理器会开始等待直到它们是可以获取的。
3 指令在合适的功能单元中被执行。
4 功能单元将运算结果写回寄存器。
乱序执行
这种范式通过以下步骤挑选可执行的指令先运行:
1 指令获取。
2 指令被发送到一个指令序列中(也称执行缓冲区或者保留站)。
3 指令将在序列中等待直到它的数据运算对象是可以获取的。然后指令被允许在先进入的、旧的指令之前离开序列缓冲区。
4 指令被分配给一个合适的功能单元并由之执行。
5 结果被放到一个序列中。
6 仅当所有在该指令之前的指令都将他们的结果写入寄存器后,这条指令的结果才会被写入寄存器中。这个过程被称为毕业或者退休周期。
乱序执行的重要概念是实现了避免计算机在用于运算的对象不可获取时的大量等待。在上述文字的要点中,乱序执行处理器避免了在顺序执行处理器处理过程第二步中当指令由于运算数据未到位所造成的等待。
例如:
b = a * 5
v = b++
c = a + 3
由于1与3可并发运行,而2之b无法随即获得,因此可以先计算乘法1与加法3,再运行2。
证明CPU乱序执行存在
public class OutOfOrder {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0; // 记录次数
for (;;) {
i++;
x = 0; y = 0;
a = 0; b = 0;
Thread one = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
one.start();two.start();
one.join();two.join();
if (x == 0 && y ==0) {
System.out.println("第"+i+"次,出现("+x+","+y+")");
break;
}
}
}
}
正常执行期待的结果不会出现x和y同时为0的情况
demo中测试的是语句级乱序的现象,语句级乱序的发生说明必定存在指令级乱序
如果出现则说明CPU存在乱序执行
结果: