Есть отличный вопрос о правиле «как будто» в общем, но мне интересно, есть ли какие-то исключения, когда дело доходит до измерения времени.
Учтите это (взято из Вот немного доработано):
using std::chrono;
auto begin = steady_clock::now();
auto result = some_lengthy_calculation(some_params);
auto end= std::chrono::steady_clock::now();
std::cout << "Time diff = " << duration_cast<microseconds>(end - begin).count() <<std::endl;
std::cout << "Result = " << result;
Компилятору разрешено применять любую оптимизацию, которая приводит к тому же result
, Дело в том, что правило «как будто» не применяется к измеренному времени напрямую. Конечно, измеренное время не должно быть постоянным при применении оптимизаций.
Поэтому мой вопрос: как я могу надежно измерять время с помощью приведенного выше кода, когда в соответствии с правилом «как будто» компилятору разрешено переставить его в одно из следующих значений?
auto temp = some_lengthy_calculation(some_params); // clever "optimization", precompute some stuff
auto begin = steady_clock::now();
auto result = temp; // yay, I can use it here to pretend to be faster
auto end = steady_clock::now();
std::cout << "Time diff = " << duration_cast<microseconds>(end - begin).count() <<std::endl;
std::cout << "Result = " << result;
или даже «более оптимизированный»:
std::cout << "Time diff = " << 42 <<std::endl;
std::cout << "Result = " << some_lengthy_calculation(some_params);
Я предполагаю, что ни один здравомыслящий компилятор не сделает этого, но что именно мешает компилятору делать такую «оптимизацию»?
TL; DR …
Для применения правила As-If компилятор должен доказать, что предлагаемое изменение не оказывает влияния на наблюдаемое поведение. Вы правы, что течение времени не является наблюдаемым поведением. Однако в случае переупорядочения функций необходимо доказать, что порядок вызова функций не влияет на наблюдаемое поведение.
Использование функций синхронизации всегда будет включать в себя некоторый механизм измерения времени, который компилятор не сможет доказать, что переупорядочивать безопасно. Например, это может быть связано с вызовом непрозрачной системной функции API или функции драйвера, которую он не может проверить. Если мы возьмем самый прозрачный пример — монотонные программные часы, которые просто увеличивают на 1 единицу времени каждый раз, когда определяется его состояние, нет способа доказать, что порядок вызовов не имеет значения, потому что он имеет значение.
Компилятор этого не сделает, вы можете быть в этом уверены.
Хотя в чистой теории это было бы разрешено, получение системного времени включает системные вызовы, которые являются полным черным ящиком для компилятора.
Компилятор не может переупорядочивать вызовы функций черного ящика, поскольку он не может предположить, что это не будет иметь побочных эффектов или содержит какие-либо барьеры.