编译器重排序
数字列表项目编译期重排序的典型就是通过调整指令顺序,在不改变程序语义的前提下,尽可能减少寄存器的读取、存储次数,充分复用寄存器的存储值。假设第一条指令计算一个值赋给变量A并存放在寄存器中,第二条指令与A无关但需要占用寄存器(假设它将占用A所在的那个寄存器),第三条指令使用A的值且与第二条指令无关。那么如果按照顺序一致性模型,A在第一条指令执行过后被放入寄存器,在第二条指令执行时A不再存在,第三条指令执行时A重新被读入寄存器,而这个过程中,A的值没有发生变化。通常编译器都会交换第二和第三条指令的位置,这样第一条指令结束时A存在于寄存器中,接下来可以直接从寄存器中读取A的值,降低了重复读取的开销。
cpu重排序
内存可见性
由于每个cpu核心都有自己的读/写缓冲区(Load,Store buffer),和cache(L1,L2等独立cache)。当一个cpu对某个变量修改时,最新的值只是放在了读/写缓冲区或者cache(并没有写回内存),并根据MESI协议(cpu缓存一致性协议)将其他cpu中该变量设置为失效。当其他cpu要访问这个变量时发现自己的cache已经失效并重新从内存中读取数据(仍然不是最新的数据)。
该问题其实就是内存可见性引起的,某个时刻修改的变量值没有及时写会主内存,导致其他线程不可见
所以需要一种协议能及时将最新值刷新到内存中。也就是“内存屏障”。 java 中的volatile 就可以认为具有内存屏障的作用。
内存屏障的作用
内存屏障的作用就是告诉编译器2件事
有屏障的地方加上一个lock 汇编指令,将之前修改且存在于缓存中的数据全部刷新到内存
这个过程中也让其他cpu的缓存失效了
屏障后面访问变量的时候,不要适用寄存器中的值,要重新从内存中读取最新值
例子
int a=0;
a++;
a++;
volatile int b=0;
b++;
b++
变量a流程是
core0 从内存读取a=0,放入寄存器中
对寄存器中的值+1
写回缓存(并同时将其他cpu的缓存设置成失效)
对寄存器中的值+1
写回缓存(并同时将其他cpu的缓存设置成失效)
变量b流程是
core0 从内存读取b=0,放入寄存器中
对寄存器中的值+1
写回内存(并同时将其他cpu的缓存设置成失效)
core0 从内存读取b=1,放入寄存器中
对寄存器中的值+1
写回内存(并同时将其他cpu的缓存设置成失效)