Мы знаем, что две инструкции могут быть переупорядочены Ооо процессор. Например, есть две глобальные переменные, общие для разных потоков.
int data;
bool ready;
Автор темы нить data
и включи флаг ready
чтобы позволить читателям использовать эти данные.
data = 6;
ready = true;
Теперь на OoOE-процессоре эти две инструкции могут быть переупорядочены (выборка команд, выполнение). Но как насчет окончательного принятия / обратной записи результатов? то есть магазин будет в порядке?
Из того, что я узнал, это полностью зависит от модели памяти процессора. Например, x86 / 64 имеет сильную модель памяти, и переупорядочение магазинов запрещено. Наоборот, ARM обычно имеет слабую модель, в которой может происходить переупорядочение магазина (наряду с несколькими другими переупорядочениями).
Кроме того, интуитивное чувство говорит мне, что я прав, потому что в противном случае нам не понадобится барьер хранилища между этими двумя инструкциями, которые используются в типичных многопоточных программах.
Но вот что наш википедия говорит:
.. В приведенном выше наброске процессор OoOE избегает остановки,
происходит на этапе (2) процессора упорядочения, когда инструкция
не полностью готов к обработке из-за отсутствия данных.Ооо процессоры заполняют эти «слоты» вовремя другими инструкциями
которые готовы, а затем изменить порядок результатов в конце, чтобы он появился
что инструкции были обработаны как обычно.
Я не совсем понимаю. Это говорит о том, что результаты должны быть записаны обратно по порядку? Действительно, в процессоре OoOE, может хранить в data
а также ready
быть переупорядочен?
Модель согласованности (или модель памяти) для архитектуры определяет, какие операции с памятью могут быть переупорядочены. Идея состоит в том, чтобы всегда достигать максимальной производительности кода, сохраняя семантику, ожидаемую программистом. В этом смысл википедии: операции с памятью показываются программисту, даже если они могут быть переупорядочены. Переупорядочение обычно безопасно, когда код однопоточный, так как процессор может легко обнаружить потенциальные нарушения.
На x86 общая модель такова, что записи не переупорядочиваются с другими записями. Тем не менее, процессор использует внеплановое выполнение (OoOE), поэтому инструкции постоянно переупорядочиваются. Как правило, процессор имеет несколько дополнительных аппаратных структур для поддержки OoOE, таких как буфер переупорядочения и очередь хранения данных. Буфер переупорядочения гарантирует, что все инструкции выполняются по порядку, так что прерывания и исключения нарушают определенную точку в программе. Очередь загрузки-хранилища работает аналогично, поскольку она может восстановить порядок операций с памятью в соответствии с моделью памяти. Очередь загрузки также устраняет неоднозначность адресов, чтобы процессор мог определить, когда выполняются операции с одинаковыми или разными адресами.
Возвращаясь к OoOE, процессор выполняет команды от 10 до 100 секунд в каждом цикле. Загрузки и хранилища вычисляют их адреса и т. Д. Процессор может предварительно выбирать строки кэша для доступа (что может включать в себя когерентность кэша), но он не может фактически получить доступ к строке для чтения или записи, пока она не станет безопасной (в соответствии с моделью памяти) ) сделать так.
Вставка барьеров магазина, ограждения памяти и т. Д. Сообщает компилятору и процессору о дальнейших ограничениях переупорядочения операций с памятью. Компилятор является частью реализации модели памяти, поскольку некоторые языки, такие как java, имеют конкретную модель памяти, в то время как другие, такие как C, подчиняются «доступам к памяти должны выглядеть так, как если бы они выполнялись по порядку».
В заключение, да, данные и готовность могут быть переупорядочены в OoOE. Но это зависит от модели памяти относительно того, являются ли они на самом деле. Поэтому, если вам нужен конкретный порядок, предоставьте соответствующую индикацию, используя барьеры и т. Д., Чтобы компилятор, процессор и т. Д. Не выбирали другой порядок для более высокой производительности.
На современном процессоре само действие сохранения является асинхронным (представьте, что вы отправляете изменение в кэш L1 и продолжаете выполнение, система кеша далее распространяется асинхронно). Таким образом, изменения в двух объектах, лежащих в другом блоке кэша, могут быть реализованы OoO с другой точки зрения ЦП.
Кроме того, даже инструкция для хранения этих данных может быть выполнена OoO. Например, когда два объекта хранятся «одновременно», но линия шины одного объекта сохраняется / блокируется другим процессором или мастером шины, таким образом, другой другой объект может быть зафиксирован ранее.
Поэтому, чтобы правильно обмениваться данными между потоками, вам нужен какой-то барьер памяти или использовать транзакционная память функция, найденная в последнем процессоре, как TSX.
Я думаю, что вы неверно истолковываете «кажется, что инструкции были обработаны как обычно». Это значит, что если у меня есть:
add r1 + 7 -> r2
move r3 -> r1
и их порядок эффективно меняется на противоположное путем выполнения, значение, которое участвует в добавлять операция все равно будет значением r1 который присутствовал до переехать. И т. Д. CPU будет кэшировать значения регистров и / или задерживать регистры, чтобы гарантировать, что «значение» последовательный поток инструкций не изменяется.
Это ничего не говорит о порядке магазинов, видимых с другого процессора.
Простой ответ — ДА на некоторых типах процессоров.
Перед ЦП ваш код сталкивается с более ранней проблемой — переупорядочением компилятора.
data = 6;
ready = true;
Компилятор свободен переставлять эти операторы, поскольку, насколько ему известно, они не влияют друг на друга (он не поддерживает потоки).
Теперь до уровня процессора:
1) Процессор, вышедший из строя, может обрабатывать эти инструкции в другом порядке, в том числе в обратном порядке.
2) Даже если процессор выполняет их по порядку, контроллер памяти может не выполнять их по порядку, поскольку ему может потребоваться очистить или ввести новые строки кэша или выполнить преобразование адреса, прежде чем он сможет их записать.
3) Даже если этого не произойдет, другой процессор в системе может не видеть их в том же порядке. Для их наблюдения может потребоваться ввести измененные строки кэша из ядра, которое их написало. Возможно, он не сможет перенести одну строку кеша раньше, чем другую, если она будет храниться в другом ядре или если существует конкуренция за эту строку из-за нескольких ядер, и ее собственное неупорядоченное выполнение будет считывать одно перед другим.
4) Наконец, спекулятивное выполнение на других ядрах может считывать значение data
до ready
был установлен ядром письма, и к тому времени, когда он доходит до чтения ready
было уже установлено но data
был также изменен.
Все эти проблемы решаются с помощью барьеров памяти. Платформы со слабо упорядоченной памятью должны использовать барьеры памяти для обеспечения согласованности памяти для синхронизации потоков.