具有记忆障碍的恒定折叠/传播优化

时间:2015-04-29 11:31:32

标签: c++ multithreading memory-barriers constantfolding

我已经阅读了一段时间,以便了解当使用现代(多核)CPU进行多线程编程时会发生什么。但是,在我阅读this时,我注意到“显式编译器障碍”部分中的代码,该部分不使用volatile来IsPublished全局。

#define COMPILER_BARRIER() asm volatile("" ::: "memory")

int Value;
int IsPublished = 0;

void sendValue(int x)
{
    Value = x;
    COMPILER_BARRIER();          // prevent reordering of stores
    IsPublished = 1;
}

int tryRecvValue()
{
    if (IsPublished)
    {
        COMPILER_BARRIER();      // prevent reordering of loads
        return Value;
    }
    return -1;  // or some other value to mean not yet received
}

问题是,在这里省略IsPublished的volatile是否安全?很多人都提到“volatile”关键字与多线程编程没什么关系,我同意它们。但是,在编译器优化期间,可以应用“常量折叠/传播”,并且如wiki page所示,如果编译器不了解谁可以更改,则可以将if (IsPublished)更改为if (false) IsPublished的值。我在这里想念或误解了什么吗?

内存障碍可能会阻止编译器的排序和CPU的无序执行,但正如我在previos段中所说,我仍然需要volatile以避免“持续折叠/传播”这是一个危险的优化特别是在无锁代码中使用全局变量作为标志?

1 个答案:

答案 0 :(得分:0)

如果调用tryRecvValue()一次,则可以安全地省略IsPublished volatile 。如果调用tryRecvValue()之间存在函数调用(编译器无法证明),它不会更改IsPublished false 值,则情况也是如此。 / p>

// Example 1(Safe)
int v = tryRecvValue();
if(v == -1) exit(1);

// Example 2(Unsafe): tryRecvValue may be inlined and 'IsPublished' may be not re-read between iterations.
int v;
while(true)
{
    v = tryRecvValue();
    if(v != -1) break;
}

// Example 3(Safe)
int v;
while(true)
{
    v = tryRecvValue();
    if(v != -1) break;
    some_extern_call(); // Possibly can change 'IsPublished'
}

只有当编译器能够证明变量的值时,才能应用常量传播。由于IsPublished被声明为非常量,因此只有在以下情况下才能证明其值:

  1. 将变量分配给给定值从变量读取后跟分支,仅在变量赋值时执行。
  2. 相同程序的主题中读取(再次)变量。

  3. 在给定程序的主题中,2到3之间的变量没有改变。

  4. 除非你在某种类型的.init函数中调用tryRecvValue(),否则编译器永远不会在同一个线程中看到 IsPublished 初始化及其读取。因此,根据其初始化来证明此变量的 false 值是不可能的。

    根据tryRecvValue函数中 false (空)分支验证错误 错误的值 在上面的代码中。