Обеспечить относительный порядок операторов для одного потока - PullRequest
0 голосов
/ 24 октября 2019

Я хочу убедиться, что следующие три оператора выполняются в указанном точном порядке:

auto t1 = std::chrono::steady_clock::now(); // Statement 1
auto t2 = std::chrono::system_clock::now(); // Statement 2
auto t3 = std::chrono::steady_clock::now(); // Statement 3

Компилятор (или процессор) может переупорядочивать эти операторы, поскольку отсутствует зависимость от данных. См. https://stackoverflow.com/a/38025837/1520427

C ++ 11 добавлено std::atomic_signal_fence, чтобы "установить порядок синхронизации памяти неатомарного и расслабленного атомарного доступа, как указано в заказе, между потоком и обработчиком сигнала, выполняемым в одном потоке«. Однако, согласно cppreference, «инструкции ЦП для упорядочения памяти не выдаются», поэтому мне неясно, как это помешает процессору переупорядочить вещи.

Мои вопросы:

Достаточно ли следующего кода, чтобы помешать компилятору переупорядочивать операторы (предполагая, что он имеет локальные определения для всего кода)?

auto t1 = std::chrono::steady_clock::now(); // Statement 1
std::atomic_signal_fence(std::memory_order::memory_order_release);
auto t2 = std::chrono::system_clock::now(); // Statement 2
std::atomic_signal_fence(std::memory_order::memory_order_release);
auto t3 = std::chrono::steady_clock::now(); // Statement 3
std::atomic_signal_fence(std::memory_order::memory_order_release);

процессор свободно переставляет порядок этих операций? Например, он может выполнить их 2-1-3? Если это так, std::atomic_thread_fence предотвратит это?

Нужно ли вводить искусственную зависимость от данных (как в связанном вопросе), чтобы получить намеченное поведение?

1 Ответ

1 голос
/ 24 октября 2019

Вам не нужно ничего делать, чтобы заблокировать переупорядочение во время компиляции;вызовы не встроенных функций - это черные ящики, которые могут взаимодействовать друг с другом через глобальные переменные, поэтому компилятор не может переупорядочить их.

Или, если std::chrono::steady_clock::now(); может полностью встроиться (возможно, используявстроенный asm для чтения метки времени), правильная реализация now() будет использовать что-то вроде volatile доступа или GNU C asm volatile, чтобы убедиться, что он не может переупорядочиваться с другими вызовами now(). (И что еще более важно, чтобы убедиться, что он не может быть CSE и быть выведенным из цикла, что приводит к иллюзии того, что все занимает ноль времени).

В отличие от вопроса, который вы связали, вещи, которые вызаботятся о порядке , а не простых вычислений, таких как z = x + y; Это вызовы специальных функций, которые обычно являются библиотечными функциями. Я не проверял спецификации, но я не удивлюсь, если у функций, получающих время, есть какое-то правило о порядке упорядочения. друг с другом. Конечно, реализация хорошего качества захочет сделать это за вас.


Может ли процессор изменить порядок этих операций?

Это полу-plausible. В отличие от реальных реализаций, обычно now() выполняет довольно много инструкций, сравнимых по размеру с окном выполнения вне порядка. (Например, размер ROB в 224 мопов на Skylake. Один rdtsc - это только 20 мопов, и есть куча масштабирующих работ).

OoO exec обычно выполняется на основе самой старой готовности к началу, поэтому несколькоповторы той же функции now() вряд ли будут работать не по порядку.

Если system_clock и steady_clock используют совершенно разные now, а now не делаетЧтобы не делать никаких барьеров, вы можете использовать механизм, специфичный для реализации, чтобы заблокировать OoO exec. например, на x86, _mm_lfence().

например, если system_clock имеет низкие издержки now, которые просто читают ячейку памяти volatile (на странице, экспортируемой ядром, обновляемой обработчиком прерываний), но steady_clock::now использует rdtsc, изменение порядка возможно. Но нет никакого портативного способа остановить это.


Однако, согласно cppreference, «инструкции ЦП для упорядочения памяти не выдаются», поэтому я неясно, как это (atomic_signal_fence) будетостановить процессор от переупорядочения вещей.

Это не так. Дело не в этом и не в чем оно. Внеочередное выполнение гарантирует сохранение иллюзии (для одного потока), что он выполнялся в программном порядке.

Следовательно, atomic_signal_fence нужно только убедиться, что порядок программы asm соответствует исходному порядку исходного кода C ++. для обработчиков сигналов, работающих в одном потоке (или обработчиков прерываний на одном и том же ядре), чтобы наблюдать за операциями этого потока, происходящими в программном порядкеИли наоборот, для магазинов, созданных обработчиком сигнала.

Вы правы, что ваша попытка не сработает. Это могло бы помочь только в (IMHO сломанной) реализации, которая позволяла переупорядочивать функции во время компиляции now() функций, и тогда, вероятно, только как побочный эффект от определения atomic_signal_fence(). например, как что-то вроде GNU C asm volatile("":::"memory"). Хотя, если now() был сломан и использует оператор не-1062 * asm (поэтому множественные вызовы now() могли бы CSE друг с другом), оператор asm volatile не упорядочил бы их.

...