为什么全局变量会导致函数调用中的编译器优化出现问题?

时间:2014-10-20 22:43:16

标签: c++ c pthreads

来自http://www.hpl.hp.com/techreports/2004/HPL-2004-209.pdf

  

防止编译器移动内存   围绕调用函数的操作   pthread mutex lock(),它们基本上被视为   调用opaque函数,关于哪个编译器   没有信息。编译器有效地假定   pthread互斥lock()可能会读或写任何内容   全局变量。 因此,无法简单地在通话中移动内存引用。此方法还可确保传递调用,例如调用函数f()   然后处理pthread mutex lock()   以相同的方式或多或少地适当地,即在f()的呼叫中不移动存储器操作   或者,无论整个用户程序是否存在   立即分析。

为什么会这样呢?为什么参考不能被移动的任何反例?

1 个答案:

答案 0 :(得分:3)

编译器可以自由移动代码。 (稍微简化)的要求是没有可见的副作用。

本文描述了为什么需要在编译器级而不是库级支持线程。因此,让我们考虑一下编译器在优化代码时的含义。我将从Kerrek SB的优秀示例开始,因为这个回复对于评论来说太长了。

int x = 0;
pthread_mutex_lock(&m);
x = y;

优化器会看到一个不会被修改但会被设置两次的值。 copmiler可以访问函数内部的代码,并且可以看到没有任何东西可以修改赋值的值。由于没有可见的副作用,优化器可以将赋值消除为零,只需将其替换为y的值即可。优化器将删除它,将其转换为:

pthread_mutex_lock(&m);
int x = y;

这可能不会影响任何事情,变量x是本地的,没有其他影响。

现在让我们做一个更有问题的人为例子。

if(globals.hasData) {
  int prelock_value = globals.foo;
  pthread_mutex_lock(&m);
  if(prelock_value != globals.foo) {
    // value changed before we could lock it, do something different
    DoSpecialStuffSinceValueChangedWhileWaiting();
    pthread_mutex_unlock(&m);
    return;
  }
  DoOtherStuff();
  ...

所以现在我们将从优化器的角度来看这个。优化器看到您读取了一个值,然后您执行了不修改该值的操作,然后根据刚刚存储的值进行测试。由于它看不到可见的副作用,它可能会删除这样的比较:

if(globals.hasData) {
  int prelock_value = globals.foo;
  pthread_mutex_lock(&m);
  if( false /* always false: prelock_value != globals.foo */ ) {
    // value changed before we could lock it, do something different
    DoSpecialStuffSinceValueChangedWhileWaiting();
    pthread_mutex_unlock(&m);
    return;
  }
  DoOtherStuff();
  ...

然后再次查看删除死代码。它看到了对整数的不必要的赋值,一个不必要的条件,因为 if 的结果总是为假,并且提出了这个:

if(globals.hasData) {
  pthread_mutex_lock(&m);
  // everything was removed.
  DoOtherStuff();

如果你将它与原始函数进行比较,很有希望这不是程序员想要的。

多年来发现了大量潜在的优化。他们中的许多人都假设何时将代码从一个地方移动到另一个地方是安全的,或者假设这些值仅由该代码块修改。这些假设在并发编程中可能会严重破坏。

优化器需要理解某些功能不能被移动,并且某些功能会成为无法移动的障碍或者使优化器假设失效的障碍。