【问题标题】:-O3 loop increment optimization-O3 循环增量优化
【发布时间】:2017-03-14 23:41:13
【问题描述】:

我有这段代码:

#include <iostream>
#include <thread>

long int global_variable;

struct process{
    long int loop_times_ = 0;
    bool op_;
    process(long int loop_times, bool op): loop_times_(loop_times), op_(op){}

    void run(){
        for(long int i=0; i<loop_times_; i++)
            if (op_) global_variable+=1;
            else global_variable-=1;
    }

};

int main(){
    struct process p1(10000000, true);
    struct process p2(10000000, false);

    std::thread t1(&process::run, p1);
    std::thread t2(&process::run, p2);
    t1.join();
    t2.join();

    std::cout <<global_variable<< std::endl;
    return 0;
}

Main 函数启动两个线程来增加和减少一个全局变量。 如果我用这个编译:

 g++ -std=c++11 -o main main.cpp -lpthread

我在每次执行中得到不同的输出。 但是如果我添加 -O3 并用这个编译:

g++ -O3 -std=c++11 -o main main.cpp -lpthread

每次输出为零

这里发生了什么样的优化来消除我的关键部分,我怎样才能欺骗编译器不优化它?

编辑:操作系统:Ubuntu 16.04.4,g++:5.4.0

【问题讨论】:

  • 你当前代码的结果可以在编译时计算出来。您是否尝试将迭代次数作为 2 个不同的参数传递给程序并像这样运行它? ./a.out 10000000 10000000
  • 我希望通过更快的优化,第一个线程甚至可能在另一个线程开始之前完成...... in 运行速度非常快吗?增加数字直到需要更长的时间,然后查看行为是否恢复。
  • 要查看进行了哪些优化,请查看生成的机器代码。检查优化和未优化版本之间的差异。
  • 那么代码实际上并没有循环——它以某种聪明的方式计算结果。我怀疑他的第一个线程正在立即完成,将全局增加 N,然后第二个线程也在做同样的事情并减少它。
  • 您使用的是什么编译器版本?什么操作系统?由于这是未定义的行为,因此这些事情很重要。如您所见,here 您的示例在不同的机器上给出了不同的结果。

标签: c++ compiler-optimization critical-section


【解决方案1】:

很可能您的运行方法正在优化为:

 void run(){
      if (op_) global_variable += loop_times_;
            else global_variable -= loop_times_;

这是编译器可以很容易地利用可用信息完成的事情。

要欺骗编译器,您必须确保循环在每次迭代时都会增加或减少 1而没有其他副作用,这一点并不明显。

尝试在循环中添加一个函数调用,它只是在名为totalIterationsDone 的对象上增加一个简单的计数器,或类似的。这可能会迫使编译器实际执行循环。将循环变量作为参数传递也可能会强制它跟踪 i 的中间值。

struct process{
    long int loop_times_ = 0;
    bool op_;
    long int _iterationsDone = 0;
    process(long int loop_times, bool op): loop_times_(loop_times), op_(op){}

    void run(){
        for(long int i=0; i<loop_times_; i++){
            if (op_) global_variable+=1;
            else global_variable-=1;
            Trick(i);
        }
    }

    void Trick(int i){
       _iterationsDone += 1;
    }    
};

【讨论】:

  • 只有当我改变技巧以将循环的 i 作为参数时,才会有不同的输出。谢谢!
  • 没问题。这实际上很有道理。我将编辑我的答案以匹配,并附上解释。
【解决方案2】:

您的程序具有未定义的行为,以数据竞争的形式出现。两个线程在不同步的情况下访问一个变量是一种数据竞争,因此是未定义的。

消除数据竞争的最简单方法是使global_variable atomic:

std::atomice<long int> global_variable;

其余代码无需进一步更改。

【讨论】:

  • 但我不希望消除数据竞争。我只是不明白为什么在 -O3 下运行时效果不可见
  • @k_kaz 未定义的行为是未定义的。关于一个展示 UB 的程序没什么可说的 - 它也可以在线订购披萨而不是使用全局变量。跨度>
  • @k_kaz 为什么你想要数据竞赛?为什么你想要未定义的行为?你知道那些被认为是坏事吗?
  • @k_kaz:在生产代码中,我同意。但在这种情况下,OP 出于兴趣试图了解幕后发生的事情。即使在未定义行为的情况下,您也可以从编译器生成的内容中学到很多东西。
  • @k_kaz 然后当我评论你的问题时,检查生成的机器代码。反汇编可执行文件,看看它做了什么。
猜你喜欢
  • 2014-07-30
  • 1970-01-01
  • 2020-03-08
  • 2021-06-27
  • 1970-01-01
  • 2023-03-29
  • 2018-07-12
  • 1970-01-01
  • 2012-02-09
相关资源
最近更新 更多