Подтвердить что ты не робот

Для {A = a; B = B; }, будет ли "A = a" строго выполняться до "B = b"?

Предположим, что A, B, A и B - все переменные, а адреса A, B, A и B все разные. Затем для следующего кода:

A = a;
B = b;

В стандарте C и С++ явно требуется, чтобы A=a выполнялся строго до B=b? Учитывая, что адреса A, B, A и B все разные, являются ли компиляторы разрешены для замены последовательности выполнения двух операторов для определенной цели, например оптимизации?

Если ответ на мой вопрос отличается на C и С++, я хотел бы знать оба.

Изменить: Фон вопроса следующий. В дизайне AI для настольных игр для оптимизации люди используют таблицу с разделяемым хэшем без блокировки, чья правильность сильно зависит от порядка выполнения, если мы не добавляем ограничение volatile.

4b9b3361

Ответ 1

Оба стандарта позволяют выполнять эти инструкции не по порядку, если это не изменяет наблюдаемое поведение. Это называется правилом as-if:

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

Ответ 2

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

Полезная ссылка

Интересная статья, охватывающая это Заказ памяти во время компиляции, и в ней говорится:

Основное правило переупорядочения памяти, которое повсеместно соблюдается разработчиками компилятора и поставщиками ЦП, можно сформулировать следующим образом:

Нельзя изменять поведение однопоточной программы.

Пример

В статье представлена ​​простая программа, в которой мы можем видеть это переупорядочение:

int A, B;  // Note: static storage duration so initialized to zero

void foo()
{
    A = B + 1;
    B = 0;
}

и показывает на более высоких уровнях оптимизации B = 0 выполняется до A = B + 1, и мы можем воспроизвести этот результат, используя godbolt, который в то время как при использовании -O3 получается следующее (видеть его в прямом эфире):

movl    $0, B(%rip) #, B
addl    $1, %eax    #, D.1624

Почему?

Почему компилятор переупорядочивает? В статье объясняется, что именно по этой причине процессор делает это из-за сложности архитектуры:

Как я уже упоминал в начале, компилятор изменяет порядок памяти взаимодействия по той же причине, что и процессор - оптимизация производительности. Такая оптимизация является прямым следствием современной сложности процессора.

Стандарты

В проекте стандарта С++ это описано в разделе 1.9 Выполнение программы, которое гласит (акцент мой вперед):

Семантические описания в этом Международном стандарте определяют параметризованной недетерминированной абстрактной машиной. Этот международный Стандарт не требует от структуры соответствия Реализации. В частности, им не нужно копировать или эмулировать структура абстрактной машины. Скорее, соответствующие реализации требуются для подражания (только) наблюдаемого поведения абстрактного машина, как описано ниже. 5

footnote 5 говорит нам, что это также известно как правило as-if:

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

проект C99 и проект стандарта C11 охватывает это в разделе 5.1.2.3 Выполнение программы, хотя мы должны пойти в индекс, чтобы увидеть, что он также называется правилом as-if в стандарте C:

as-if, 5.1.2.3

Обновление по соображениям блокировки

В статье "Введение в программирование без блокировки" хорошо освещается этот вопрос и для проблем с OPs при реализации без блокировки с разделяемой хэш-таблицей это раздел, вероятно, наиболее уместен:

Заказ памяти

Как следует из блок-схемы, любое время, когда вы выполняете программирование без блокировки для multicore (или любой симметричный многопроцессор), и ваша среда делает не гарантируют последовательную согласованность, вы должны учитывать, как предотвратить переупорядочение памяти.

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

  • Легкая инструкция синхронизации или забора, о которой я расскажу в будущих сообщениях;
  • Инструкция по полной записи памяти, которую Ive продемонстрировал ранее;
  • Операции памяти, которые обеспечивают получение или выпуск семантики.

Получение семантики предотвращает переупорядочение памяти операций, которые следуют его в программном порядке, а семантика релиза предотвращает переупорядочение памяти предшествующих ему операций. Эти семантики особенно подходят в случаях, когда отношения производителей/потребителей, когда один поток публикует некоторую информацию, а другой читает ее. Ill также поговорите об этом больше в будущем посте.

Ответ 3

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

Ответ 4

Так как A = a; и B = b; независимы в терминах зависимостей данных, это не имеет значения. Если был результат/результат предыдущей инструкции, влияющей на последующий ввод инструкции, тогда упорядочение вопросов, в противном случае нет. это строго последовательное выполнение.

Ответ 5

Я читал, что это требуется для работы по стандарту С++; однако, если вы пытаетесь использовать это для многопоточного управления, это не работает в этом контексте, потому что здесь нет ничего, чтобы гарантировать, что регистры будут записаны в память в правильном порядке.

Как показывает ваше изменение, вы пытаетесь использовать его точно там, где он не будет работать.

Ответ 6

Может показаться, что если вы сделаете это:

{ A=a, B=b; /*etc*/ }

Обратите внимание на запятую вместо точки с запятой.

Тогда спецификация С++ и любой подтверждающий компилятор должны будут гарантировать порядок выполнения, поскольку операнды оператора запятой всегда оцениваются слева направо. Это действительно можно использовать, чтобы предотвратить оптимизацию оптимизатора синхронизации потоков путем переупорядочения. Запятая эффективно становится барьером, через который переупорядочение не допускается.