编译器的过度优化CPU乱序执行

Posted flappydemo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译器的过度优化CPU乱序执行相关的知识,希望对你有一定的参考价值。

编译器会对我们的程序进行优化,不同的编译器有所区别,有时候编译器的优化以及CPU的乱序执行会给我们带来一些错误,尤其是多线程的时候。(CPU乱序执行是什么,其实没有那么吓人,看到后面就会知道啦)

接下来我们分析几个简单的程序:

1、int x=0;

//thread1                //thread2

lock();                       lock();

x++;                           x++;

unlock();                  unlock();

线程1和2分别对x加锁访问,看起来是没有问题的,但是,我们知道x++分为三步:将x赋值给寄存器R1,将R1++,然后返回给x。但是编译器很有可能将x放到R1后,由于后面还会访问x而不将R1返回给x,因此线程2在访问x时,x的值仍为0。可见此时即使使用了加锁机制,也不能保证线程安全。

解决方法:使用volatile关键字来阻止编译器为了提高速度而将一个变量缓存到寄存器而不返回

int x=0;

//thread1                //thread2

lock();                      lock();

volatile x++;             volatile x++;

unlock();                  unlock();

2、int x=0,y=0;

//thread1                //thread2

x=1;                           y=1;

r1=y;                         r2=y;

这种情况下,编译器很可能会为了效率而交换两条不相干的指令,很可能编译器先执行r1=y再执行x=1,结合1中的分析,最终r1=0, r2=0也不是没有可能的

解决方法:使用volatile关键字来阻止编译器操作volatile变量的指令顺序

                 volatile int x=0,y=0;

3、int func(int&a){

int b=a;

int c=a;

return a+b+c;

}

int main(){

int a=1;

//.....doSomething与a无关

return func(a);

}

对于上述代码,编译器会将其优化为:

int main(){

return 3;

}

为什么,因为编译器知道a值为1,参考上下文,编译器也能知道b、c的值也为1,而且没有其他人使用或修改a、b、c三个变量,因此直接进行如此优化。但是,假如我们考虑多线程,此时如果另一个线程改了a的值,如果还按上述方式优化就会有问题

解决方法:用volatile关键字告诉编译器此处的变量a很有可能被其他地方改变,不能进行优化

int func(volatile int&a){

int b=a;

int c=a;

return a+b+c;

}

int main(){

volatile int a=1;

//.....doSomething与a无关

return func(a);

}

4、static int* instance=0;

int& getInstance(){

if(!instance){

lock();

if(!instance){

instance=new int;

}

unlock();

}

return *instance;

}

对于上述代码,熟悉单例模式的同学们应该不陌生,在这里,我们使用了两次判断,但是,编译器很有可能会认为第一次判断成功以后没有必要再去做第二次判断,因为编译器会认为instance的值在这两次判断之间并没有改变(编译器是不会去考虑另一个线程有没有来改变instance的值,它只看到这一段代码),但是这里的两次判断是有其作用的,我们并不希望编译器做这样的优化

解决方法:用volatile关键字告诉编译器此处的变量instance很有可能被其他地方改变,不能进行优化

                 static int* volatile instance=0;

5、上面的代码看起来没有问题了,编译器也不会对其进行我们不希望的优化了,但是,我们会发现还有另一个CPU乱序执行问题:

解决方法:使用barrier指令阻止CPU将该指令之前的指令交换到该指令之后

#define barrier() _asm_  volatile ("lwsync")

static int* volatile instance=0;

int& getInstance(){

if(!instance){

lock();

if(!instance){

int* temp=new int;

barrier();

instance=temp;

}

unlock();

}

return *instance;

}

综上所述:

1、volatile可以阻止编译器为了提高速度而将一个变量缓存到寄存器而不返回

2、volatile可以阻止编译器操作volatile变量的指令顺序

3、volatile可以告诉编译器此处的变量很有可能被其他地方改变,不能进行优化

4、使用barrier指令阻止CPU将该指令之前的指令交换到该指令之后

注意:

1、volatile只是用于阻止编译器的一部分优化机制,与锁完全无关,线程安全不能依靠volatile来实现,最多只作为线程安全的额外考虑

2、要保证线程安全,即使我们用了锁等同步操作,我们仍虚考虑CPU的乱序执行,要保证线程安全,在适当的地方阻止CPU换序是必要的


不足的地方欢迎大家补充和指正 


以上是关于编译器的过度优化CPU乱序执行的主要内容,如果未能解决你的问题,请参考以下文章

关于内存屏蔽 和内存泄漏的解决

一道面试题目

linux内核源码分析之内存屏障和RCU机制

java代码乱序问题

Linux 内核 内存管理优化内存屏障 ① ( barrier 优化屏障 | 编译器优化 | CPU 执行优化 | 优化屏障源码 barrier 宏 )

Linux 内核 内存管理优化内存屏障 ① ( barrier 优化屏障 | 编译器优化 | CPU 执行优化 | 优化屏障源码 barrier 宏 )