Проблема оптимизации с атрибутом функции [[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, могут безопасно читать любые энергонезависимые объекты и изменять значение объектов таким образом, чтобы это не влияло на их возвращаемое значение или наблюдаемое состояние программы.