JMM - 重排序

作者: Zeppelin421 | 来源:发表于2022-06-20 16:09 被阅读0次

计算机在执行程序时,为了提高性能,编译器和处理器尝尝会对指令做重排

编译器优化重排

编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。属于编译器重排

// 优化前
int x = 1;
int y = 2;
int a1 = x * 1;
int b1 = y * 1;
int a2 = x * 2;
int b2 = y * 2;

// 优化后
int x = 1;
int y = 2;
int a1 = x * 1;
int a2 = x * 2;
int b1 = y * 1;
int b2 = y * 2;

CPU只读一次的x和y值,不需反复读取寄存器来交替x和y值

指令并行的重排

现代处理器采用了指令级并行技术来讲多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行语句的结果),处理器可以改变语句对应的机器指令的执行顺序。属于处理器重排

从指令的执行角度来说,一条指令可以分为多个步骤完成:

取指:IF
译码和取寄存器操作数:ID
执行或者有效地址计算:EX(ALU逻辑计算单元)
存储器访问:MEM
写回寄存器:WB

CPU在工作时,需要将上述指令分为多个步骤依次执行,由于每一步会使用到不同的硬件操作,比如取指会只有PC寄存器和存储器,译码会执行到指令寄存器组,执行时会执行ALU、写回时使用到寄存器组。为了提高硬件使用率,CPU指令是按流水线来执行


处理器指令流水线执行过程

虽然流水线可以大大提升CPU的性能,但是一旦出现流水中断,所有硬件将会进入一轮停顿期,当再次弥补中断点可能需要几个周期,这种性能损失也会很大,因此需要尽量阻止指令中断的情况,指令重排就是其中一种优化中断的手段。
例如:

i = a + b;
y = c - d;
i = a + b; y = c - d; 的指令流水执行过程(未排序)
上述执行过程在某些指令上存在X的标志,X代表中断的含义。为什么停顿呢?这是因为部分数据还没有准备好,如执行ADD指令时,需要使用到前面指令的数据R1、R2,而此时R2的MEM操作还没有完成。想要消除这些停顿,就要用到指令重排。如下图,既然ADD指令需要等待,那就利用等待的时间做些别的事,如把LW R4,cLW R5,d移动到前面执行,毕竟LW R4,cLW R5,d执行并没有数据依赖关系
i=a+b;y=c-d;的指令流水线执行过程(指令重排序)

内存系统的重排

由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行,因为缓存的存在,导致内存和缓存的数据同步存在时间差。属于处理器重排

CPU缓存一致性协议,例如MESI,多个CPU核心之间缓存不会出现不同步的问题,不会有“内存可见性”问题。但缓存一致性协议对性能有很大损耗,为了解决这个问题,又进行了各种优化,例如增加Store Buffer、Load Buffer等。Store Buffer的延迟写入是重排序的一种,也称之为“内存重排序”,是造成“内存可见性”问题的主因。

// 线程1                线程2
X = 1;                 Y = 1;
a = Y;                 b = X;

假设X、Y是两个全局变量,初始时候,X = 0, Y = 0。线程1和线程2的执行先后顺序是不确定,可能顺序执行,也可能交叉执行,最终结果可能是:

1. a = 0, b = 1
2. a = 1, b = 0
3. a = 1, b = 1

但实际上还有一种可能是:a = 0, b = 0

两个线程的指令都没有重排序,执行顺序就是代码的顺序,但仍然可能出现a=0, b=0。原因是线程1先执行X=1后执行a=Y,但此时X=1还在自己的Store Buffer里面,没有及时写入主内存中。所以线程2看到的X还是0。线程2的道理与此相同。

虽然线程1觉得自己是按代码顺序正常执行的,但在线程2看来,a=Y和X=1顺序却是颠倒的。指令没有重排序,是写入内存的操作被延迟了,也就是内存被重排序了,这就造成内存可见性问题。

解决方案

为了保证内存可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序

相关文章

  • JMM - 重排序

    计算机在执行程序时,为了提高性能,编译器和处理器尝尝会对指令做重排 编译器优化重排 编译器在不改变单线程程序语义的...

  • JMM之重排序

    重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段 在程序执行时,为了提高性能,编译器和处...

  • JMM之重排序

    重排序定义 在前面我们提到过,重排序是编译器和处理器为了优化程序性能而对指令序列重新排序的一种手段。但是我们也知道...

  • JMM内存模型之happens-before

    JMM把happens-before 要求禁止的重排序分为了下面两类。 会改变程序执行结果的重排序。 不会改变程序...

  • volatile

    一、volatile可见性和禁止重排序是怎么实现的? 首先从JMM内存模型层面,volatile通过storest...

  • 数组排序去重

    冒泡排序 sort排序 数组去重

  • JVM结构和Java内存模型

    //TODOJVM结构:堆方法区栈本地方法栈程序计数器 栈帧结构: JMM内存模型:happen before 重排序

  • 再学JS--数组去重

    双层循环 最原始的数组去重方式 indexOf 排序后去重 排序去去重是将数组进行sort排序,相同的值就会被排在...

  • JAVA内存模型--指令重排序

    指令重排序 在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。但是,JMM确保在不同的编译器和不同的处理...

  • JAVA内存模型---重排序

    JMM内存模型的重排序是指在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序(看到这句话时,我就想到了...

网友评论

    本文标题:JMM - 重排序

    本文链接:https://www.haomeiwen.com/subject/lmvcvrtx.html