[[gnu::pure]] 函数属性和线程的优化问题

Optimization problem with [[gnu::pure]] function attribute and threads

我有一个程序几乎立即在 gcc 上以 -O0 结束,但在 gcc 和 -O3 上永远挂起。如果我删除 [[gnu::pure]] 函数属性,它也会立即退出,即使该函数不修改全局状态。该程序在三个文件中:

thread.hpp

#include <atomic>

extern ::std::atomic<bool> stopthread;

extern void threadloop();
[[gnu::pure]] extern int get_value_plus(int x);

thread.cpp

#include <thread>
#include <atomic>
#include "thread.hpp"

namespace {
::std::atomic<int> val;
}

::std::atomic<bool> stopthread;

void threadloop()
{
   while (!stopthread.load())
   {
      ++val;
   }
}

[[gnu::pure]] int get_value_plus(int x)
{
   return val.load() + x;
}

main.cpp

#include <thread>
#include "thread.hpp"

int main()
{
   stopthread.store(false);
   ::std::thread loop(threadloop);

   while ((get_value_plus(5) + get_value_plus(5)) % 2 == 0)
      ;
   stopthread.store(true);
   loop.join();
   return 0;
}

这是编译器错误吗?缺少有关使用 [[gnu::pure]] 的正确注意事项的文档?对 [[gnu::pure]] 的文档的误读以至于我编写了一个错误?

I have a program which nearly immediately finishes with -O0 on gcc, but hangs forever with gcc and -O3

是的,因为启用优化后程序会被编译成无限循环。

Is this a compiler bug? A lack of documentation for the proper caveats to using [[gnu::pure]]? A misreading of the documentation for [[gnu::pure]] such that I've coded a bug?

这不是编译器错误。 get_value_plus 不是 pure 函数:

[[gnu::pure]] int get_value_plus(int x)
{
    return val.load() + x;
}

因为 return 值可以随时更改(对于相同的 x),因为 val 预计会被其他线程修改。

然而,编译器认为 get_value_plus 将始终 return 相同的值,将执行 CSE 并因此假设:

while ((get_value_plus(5) + get_value_plus(5)) % 2 == 0);

可以写成:

int x = get_value_plus(5);
while ((x + x) % 2 == 0);

实际上,无论 x 的值如何,它都是一个无限循环:

while (true);

详情请见GCC documentation on pure

一般情况下,除非很好理解,否则请避免使用优化提示!

在这种情况下,误解是 pure 允许函数读取全局内存,但如果该内存在调用者以外的其他人的调用之间发生变化,则不允许:

However, functions declared with the pure attribute can safely read any non-volatile objects, and modify the value of objects in a way that does not affect their return value or the observable state of the program.

事实证明,我误读了文档。来自关于 gcc 中 pure 属性的在线文档:

The pure attribute prohibits a function from modifying the state of the program that is observable by means other than inspecting the function’s return value. However, functions declared with the pure attribute can safely read any non-volatile objects, and modify the value of objects in a way that does not affect their return value or the observable state of the program.

和一个不同的段落:

Some common examples of pure functions are strlen or memcmp. Interesting non-pure functions are functions with infinite loops or those depending on volatile memory or other system resource, that may change between consecutive calls (such as the standard C feof function in a multithreading environment).

这两段清楚地表明我一直在欺骗编译器,我写的函数不符合'pure'的条件,因为它依赖于一个随时可能改变的变量。

我问这个问题的原因是因为这个问题的答案: 根本没有解决这个问题(当时我问了我的问题)。最近的 C++ Weekly 插曲有一条评论询问线程和纯函数。所以很明显那里有些混乱。

因此,符合此标记条件的函数的标准是它不得修改全局状态,但允许读取它。但是,如果它确实读取了全局状态,则不允许读取任何可以被视为 'volatile' 的全局状态,并且最好将其理解为可能在两次立即连续调用函数之间发生变化的状态,即如果声明它的读数在这种情况下可能会发生变化:

f();
f();