Зачем очищать конвейер для нарушения порядка памяти, вызванного другими логическими процессорами? - PullRequest
5 голосов
/ 07 апреля 2019

Событие производительности Machine Order Machine Clear описывается в документации vTune как:

Очистка машины упорядочения памяти (MO) происходит при совпадении запроса отслеживания от другого процессора.источник для операции с данными в конвейере.В этой ситуации конвейер очищается до того, как загруженные грузы и склады удаляются.

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

также описана проблема здесь

Очистка машины упорядочения памяти запускается всякий раз, когда ядро ​​ЦП обнаруживает «конфликт упорядочения памяти».По сути, это означает, что некоторые из ожидающих выполнения инструкций пытались получить доступ к памяти, о которой мы только что обнаружили какое-то другое ядро ​​ЦП, записанное за это время.Поскольку эти инструкции все еще помечены как ожидающие, в то время как событие «эта память только что была записана» означает, что какое-то другое ядро ​​успешно завершило запись, ожидающие инструкции - и все, что зависит от их результатов - задним числом неверны: когда мы начали выполнять этиинструкции, мы использовали версию содержимого памяти, которая сейчас устарела.Таким образом, мы должны бросить всю эту работу и сделать это снова.Это понятная машина.

Но это не имеет смысла для меня, CPU не нужно повторно выполнять нагрузки в очереди загрузки, так как нет общего порядка для незафиксированных нагрузок /магазины.

Я мог видеть проблему в том, что нагрузкам разрешалось переупорядочивать:

;foo is 0
mov eax, [foo]    ;inst 1
mov ebx, [foo]    ;inst 2
mov ecx, [foo]    ;inst 3

Если порядок выполнения будет 1 3 2, то магазин, такой как mov [foo], 1 между 3 и 2, вызовет

eax = 0
ebx = 1
ecx = 0

, что действительно нарушает правила упорядочения памяти.

Но нагрузки не могут быть переупорядочены с нагрузками, так почему процессоры Intel сбрасывают конвейер, когда запрос отслеживания от другого ядра совпадает с источникомлюбая нагрузка в полете?
Какие ошибочные ситуации предотвращает такое поведение?

1 Ответ

5 голосов
/ 07 апреля 2019

Хотя модель упорядочения памяти x86 не позволяет глобально наблюдать загрузки любого типа памяти, кроме WC, в программном порядке, реализация фактически позволяет завершать загрузки не в порядке. Было бы очень дорого останавливать выдачу запроса на загрузку, пока все предыдущие загрузки не будут завершены. Рассмотрим следующий пример:

load X
load Y
load Z

Предположим, что строка x отсутствует в иерархии кэша и должна быть извлечена из памяти. Тем не менее, оба Y и Z присутствуют в кэше L1. Один из способов соблюдения требований к упорядочению нагрузки для x86 - не выдавать нагрузки Y и X до тех пор, пока нагрузка X не получит данные. Однако это приведет к остановке всех инструкций, которые зависят от Y и Z, что приведет к потенциально значительному снижению производительности.

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

  • Загрузка завершена, пока предыдущая загрузка в программном порядке все еще ожидает своих данных, и эти две загрузки относятся к типу памяти, который требует упорядочения.
  • Другое физическое или логическое ядро ​​изменило строку, считанную более поздней загрузкой, и это изменение было обнаружено логическим ядром, которое выдало нагрузки до того, как более ранняя загрузка получила свои данные.

Когда выполняются оба эти условия, логическое ядро ​​обнаруживает нарушение порядка в памяти. Рассмотрим следующий пример:

------           ------
core1            core2
------           ------
load rdx, [X]    store [Y], 1
load rbx, [Y]    store [X], 2
add  rdx, rbx
call printf

Предположим, что начальное состояние:

  • [X] = [Y] = 0.
  • Строка кэша, содержащая Y, уже присутствует в L1D ядра core1. Но X не присутствует в частных кешах core1.
  • Линия X присутствует в L1D core2 в состоянии изменяемой когерентности, а линия Y присутствует в L1D core2 в совместном состоянии.

В соответствии с моделью строгого упорядочения x86, единственно возможными правовыми исходами являются 0, 1 и 3. В частности, результат 2 не является законным.

Может произойти следующая последовательность событий:

  • Core2 выдает RFO для обеих линий. RFO для строки X завершится быстро, но RFO для строки Y придется пройти до L3, чтобы сделать недействительной строку в частных кэшах core1. Обратите внимание, что core2 может фиксировать хранилища только по порядку, поэтому хранилище в строку X ожидает, пока хранилище в строку Y не завершится.
  • Core1 выдает две нагрузки на L1D. Загрузка из строки Y завершается быстро, но загрузка из X требует извлечения строки из частных кешей core2. Обратите внимание, что значение Y в этой точке равно нулю.
  • Строка Y является недействительной из частных кэшей core1, а ее состояние в core2 изменяется на изменяемое состояние когерентности.
  • Core2 теперь фиксирует оба магазина по порядку.
  • Строка X пересылается с core2 на core1.
  • Core1 загружает из строки кэша X значение, хранящееся в core2, которое равно 2.
  • Core1 печатает сумму X и Y, которая равна 0 + 2 = 2. Это неверный результат. По сути, в core1 загружено устаревшее значение Y.

Чтобы поддерживать порядок загрузок, загрузочный буфер core1 должен отслеживать все недействительные строки в резидентных кешах. Когда он обнаруживает, что строка Y была признана недействительной, в то время как существуют ожидающие загрузки, которые предшествуют завершенной загрузке из недействительной строки в программном порядке, происходит нарушение порядка в памяти, и нагрузка должна быть переиздана, после чего она получает самое последнее значение. Обратите внимание, что если строка Y была удалена из частных кешей core1 до того, как она была признана недействительной, и до того, как загрузка из X завершилась, она может быть не в состоянии отследить аннулирование строки Y в первую очередь. Таким образом, должен быть механизм, чтобы справиться и с этой ситуацией.

Если core1 никогда не использует одно или оба загруженных значения, нарушение порядка загрузки может произойти , но оно никогда не может наблюдаться .Точно так же, если значения, хранящиеся в core2 в строках X и Y, совпадают, нарушение порядка загрузки может произойти, но это невозможно наблюдать.Однако даже в этих случаях core1 все равно излишне переиздает нарушающую нагрузку и воспроизведет все ее зависимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...