Проблема оптимизации с атрибутом функции [[gnu::pure]] и потоками

У меня есть программа, которая почти сразу заканчивается -O0 на gcc, но навсегда зависает с 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]] такой, что я закодировал ошибку?

2 ответа

Решение

Оказывается, я неправильно прочитал документацию. Из онлайн-документации о pure атрибут в gcc:

Атрибут pure запрещает функции изменять состояние программы, которое можно наблюдать, кроме проверки возвращаемого значения функции. Однако функции, объявленные с атрибутом pure, могут безопасно читать любые энергонезависимые объекты и изменять значение объектов таким образом, чтобы это не влияло на их возвращаемое значение или наблюдаемое состояние программы.

и другой абзац:

Некоторыми распространенными примерами чистых функций являются strlen или memcmp. Интересными не чистыми функциями являются функции с бесконечными циклами или функции, зависящие от энергозависимой памяти или другого системного ресурса, которые могут переключаться между последовательными вызовами (например, стандартная функция C feof в многопоточной среде).

Эти два параграфа проясняют, что я лгал компилятору, и написанная мной функция не квалифицируется как "чистая", поскольку она зависит от переменной, которая может измениться в любое время.

Причина, по которой я задал этот вопрос, заключается в том, что ответы на этот вопрос: __attribute __ ((const)) vs __attribute __ ((pure)) в GNU C вообще не решали эту проблему (в то время я все равно задавал свой вопрос). А в недавнем еженедельном выпуске C++ был комментарий с вопросом о потоках и чистых функциях. Так что ясно, что есть некоторая путаница.

Таким образом, критерий для функции, которая подходит для этого маркера, заключается в том, что он не должен изменять глобальное состояние, хотя ему разрешено читать его. Но, если он читает глобальное состояние, ему не разрешается читать любое глобальное состояние, которое можно считать "изменчивым", и это лучше всего понимать как состояние, которое может измениться между двумя сразу последовательными вызовами функции, т. Е. Если это состояние чтение может измениться в такой ситуации:

f();
f();

У меня есть программа, которая почти сразу заканчивается -O0 на gcc, но навсегда зависает с gcc и -O3

Да, потому что программа включается в бесконечный цикл при включенной оптимизации.

Это ошибка компилятора? Отсутствие документации для правильного использования [[gnu::pure]]? Неправильное прочтение документации для [[gnu::pure]] такой, что я закодировал ошибку?

Это не ошибка компилятора. get_value_plus это не pure функция:

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

так как возвращаемое значение может измениться в любое время (для того же x), так как val как ожидается, будет изменен другим потоком.

Компилятор, однако, думает, что get_value_plus всегда будет возвращать одно и то же значение, выполнит 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 наpure Больше подробностей.

В общем, избегайте использования подсказок по оптимизации, если они не будут хорошо поняты!

В этом случае недоразумение заключается в том, что pure функциям разрешено считывать глобальную память, но только если эта память изменяется от вызова к вызову кем-то, кроме вызывающей стороны:

Однако функции, объявленные с атрибутом pure, могут безопасно читать любые энергонезависимые объекты и изменять значение объектов таким образом, чтобы это не влияло на их возвращаемое значение или наблюдаемое состояние программы.

Другие вопросы по тегам