Что происходит с хранилищем, «проигравшим гонку» за разделяемую память в модели памяти x86 TSO?

Я знаю, что процессоры x86 используют модель памяти TSO, и меня интересует одна вещь. Я объясню это на примере.

У нас есть два процессора (P1 и P2), где P1 сохраняет X=1 в свой буфер хранилища, а P2 сохраняет X=2 в свой буфер хранилища. Если P1/P2 читает X, он сначала обращается к своему буферу хранилища, а затем к общей памяти. Поскольку X находится в буфере хранилища, P1 будет читать 1, а P2 — 2. Однако, если какое-либо из этих хранилищ попадает в разделяемую память, оно становится значением, которое будут читать оба процессора. Например, если X=2 (из буфера хранилища P2) поступает первым в разделяемую память, и P1, и P2 будут читать 2 (P1 больше не будет читать 1).

Мой вопрос: что происходит с хранилищем (X=1) в другом буфере хранилища (P1)? Другими словами, что произойдет с магазином, который «проиграл гонку за разделяемую память»? Он каким-то образом удален или остается в буфере хранилища и в конечном итоге найдет свой путь в общую память, поэтому и P1, и P2 в какой-то момент начнут читать 1 вместо 2?

Расс Кокс (Голанг) в своей статье о модели аппаратной памяти написал следующее:

Все процессоры по-прежнему подключены к одной общей памяти, но каждая очередь процессора записывает в эту память в локальную очередь записи. Процессор продолжает выполнять новые инструкции, пока выполняется запись. пробиваются в общую память. Память, прочитанная на одном процессор обращается к локальной очереди записи, прежде чем обращаться к основной памяти, но не видит очереди записи на других процессорах. Эффект заключается в том, что процессор видит свои записи раньше, чем другие. **Но — и это очень важно — все процессоры согласны относительно (общего) порядок, в котором записи (сохранения) достигают общей памяти, давая модель, ее название: общий заказ магазина или TSO. На данный момент запись достигает общей памяти, любое будущее чтение на любом процессоре увидит ее и используйте это значение (пока оно не будет перезаписано при последующей записи или возможно, путем буферизованной записи с другого процессора).

Путаница возникает из-за этих двух выделенных жирным шрифтом частей, поскольку они кажутся мне противоречивыми. В первом Расс говорит, что чтение сначала обращается к буферу, а затем к общей памяти, а во втором он говорит, что запись в буфер может перезаписать предыдущую запись. Таким образом, я в замешательстве. Если он сначала потребляет буфер, а затем общую память, как процессор, «который проиграл гонку магазинов» (P1), сможет увидеть запись процессора, «который выиграл гонку магазинов» (P2)? Он может только видеть, сделано ли какое-либо обновление в его буфере. Однако тогда второй текст, выделенный жирным шрифтом, не имеет смысла, поскольку буферизованная запись (X=1) больше не существует, и с помощью «пока она не будет перезаписана более поздней записью» он охватывает любую более позднюю запись.

Связанный: Инструкции по загрузке Globally Invisible обсуждают странные эффекты, возникающие при пересылке в магазин. Ядро видит свои собственные хранилища до того, как они станут глобально видимыми; пока сохранение все еще находится в буфере хранилища, это последнее сохранение в этом месте из PoV этого ядра. Как только он в конечном итоге фиксируется (после получения эксклюзивного права владения строкой кэша через MESI «чтение для владения»), он становится частью глобального общего порядка хранилищ, поскольку кэши являются согласованными.

Peter Cordes 05.07.2024 03:03

@PeterCordes Я не понимаю. Если X=2 из буфера P2 записывается в разделяемую память раньше X=1 (хранится в буфере P1), что увидит P1 при чтении X (1 из своего буфера или 2 из общей памяти)? Кроме того, что происходит с X=1 в буфере P1, когда X=2 записывается в общую память?

Cosmos 05.07.2024 03:25

Загрузки из P1, которые находятся в программном порядке до сохранения P1, будут видеть X=2 (или какое-то более раннее значение, если они читают кэш еще до фиксации хранилища P2). Загрузки из P1, находящиеся в программном порядке после сохранения, будут видеть X=1. Это остается верным даже после того, как это хранилище фиксирует данные в кеше, а P1 считывает их из кеша вместо пересылки в хранилище. т. е. хранилище P1 идет после хранилища P2 в порядке модификации для этого местоположения (терминология C/C++), с точки зрения всех ядер, включая P1.

Peter Cordes 05.07.2024 07:43

Все становится интереснее (с точки зрения эффектов), когда несколько ядер имеют ожидающие сохранения в одном и том же месте. Тогда два ядра смогут считывать разные значения для одного и того же места. Какой бы из них ни был зафиксирован вторым, он будет долгосрочным значением для этого местоположения, и другое ядро ​​начнет видеть это значение. Как и следовало ожидать от ядра, которое сделало магазин, который оказался первым. Это действительно становится странно, когда у вас также есть магазины в нескольких местах, поскольку эффекты не совсем переупорядочивают StoreLoad.

Peter Cordes 05.07.2024 07:50

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

Peter Cordes 05.07.2024 07:51

@PeterCordes Спасибо, чувак! У меня один вопрос "вырванный из контекста". Поскольку я хочу изучить все эти вещи подробно, знаете ли вы какую-нибудь хорошую книгу, которую вы могли бы порекомендовать? Я думал о «Базовом руководстве по согласованности памяти и согласованности кэша», но я не уверен, поскольку в нем не упоминаются термины, которые вы использовали («переупорядочение StoreLoad», «перенаправление хранилища»). Заранее спасибо!

Cosmos 05.07.2024 12:59

Отличной отправной точкой для переупорядочения памяти и таких терминов, как переупорядочение StoreLoad, являются preshing.com/20120710/… и preshing.com/20120913/acquire-and-release-semantics . Статья в Википедии MESI в порядке, но описание с точки зрения отслеживания общей шины вводит в заблуждение по сравнению с тем, что на самом деле делают современные процессоры («каталог» отслеживает, какая строка находится, чтобы знать, куда отправлять недействительные сообщения / RFO). Я узнал об этом не из книги, поэтому ИДК, хотя это хороший комп. арх. текст является хорошим фоном.

Peter Cordes 05.07.2024 18:41

@PeterCordes И еще один вопрос. Это «официальный документ x86-TSO» cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf ? Я также нашел это ( cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.tphols.pdf), но, как я вижу, это всего лишь предложение (которое принято) и первая статья/ссылка это тот, который мне следует посмотреть/изучить (кстати, обе статьи написали одни и те же люди из Кембриджа).

Cosmos 05.07.2024 18:56

Это формальное определение модели памяти совсем непросто понять. Это не очень хорошая отправная точка. Хотя, если еще раз просмотреть статью, можно увидеть немало текста, обсуждающего ее, так что, возможно, не так уж и плохо. Неформальное понимание основ того, как все работает, очень полезно для того, чтобы попытаться выяснить, что именно пытается определить формализм. Такие вещи, как документация по модели памяти ядра Linux, могут оказаться полезными (хотя переупорядочение во время компиляции является здесь серьезной проблемой, например, lwn.net/Articles/793253, в отличие от написания на asm и размышлений о процессорах).

Peter Cordes 05.07.2024 21:09

Wiki тега x86 ссылки cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.pdf, «расширенная версия» этой статьи, 2009 года. Кажется, я видел что-то о обновленную версию этой статьи за последние пару лет, но сейчас я не могу ее найти. Давненько я на это не смотрел; Мне было достаточно понять это как программный порядок плюс буфер хранилища с переадресацией хранилища. (Наряду с тем, что IRIW невозможен, так как нет побочного канала, по которому некоторые ядра могли бы видеть хранилища других раньше других, как в POWER)

Peter Cordes 05.07.2024 21:17
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
11
70
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

То, о чем вы спрашиваете, описано в основах согласованности кэша MESI, и ответ не меняется даже при ослабленном порядке памяти, таком как ARMv8. (Или как в модели памяти ISO C++.) Какое бы ядро ​​первым ни получило эксклюзивное право владения строкой кэша, оно может сначала зафиксировать свое хранилище, а затем второе ядро ​​перезаписывает это значение своим хранилищем. Загрузки видят значение в порядке модификации для этого местоположения.

Если оба ядра имеют ожидающие хранилища, порядок модификации еще не установлен, но эти ядра будут видеть свои собственные хранилища посредством перенаправления хранилища. (Другие ядра не будут видеть хранилища, которые еще не зафиксированы в кэше L1d, потому что это единственный способ для хранилищ вообще стать видимыми для других ядер в модели памяти, которая не позволяет переупорядочение IRIW. Примите Кэш L1d — это когда хранилище становится глобально видимым.)

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

Если X=2 из буфера P2 записан в разделяемую память до X=1 (хранится в буфере P1), что увидит P1 при чтении X (1 из своего буфера или 2 из общей памяти)? Кроме того, что происходит с X=1 в буфере P1, когда X=2 записывается в разделяемую память?

  • Загрузки из P1, которые находятся в программном порядке до сохранения P1, будут видеть X=2 (или какое-то более раннее значение, если они читают кэш еще до фиксации хранилища P2).

  • Загрузки из P1, находящиеся в программном порядке после сохранения, будут видеть X=1. Это остается верным даже после того, как это хранилище фиксирует данные в кеше, а P1 считывает их из кеша вместо пересылки в хранилище. т. е. хранилище P1 идет после хранилища P2 в порядке модификации для этого местоположения (терминология C/C++), с точки зрения всех ядер, включая P1.

Все становится интереснее (с точки зрения эффектов), когда несколько ядер имеют ожидающие сохранения в одном и том же месте. Тогда два ядра смогут считывать разные значения для одного и того же места. Какой бы из них ни был зафиксирован вторым, он будет долгосрочным значением для этого местоположения, и другое ядро ​​начнет видеть это значение. Как и следовало ожидать от ядра, которое сделало магазин, который оказался первым.

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

Связанный: Инструкции по загрузке Globally Invisible обсуждают странные эффекты, возникающие при пересылке в магазин. Ядро видит свои собственные хранилища до того, как они станут глобально видимыми; пока сохранение все еще находится в буфере хранилища, это последнее сохранение в этом месте из PoV этого ядра. Как только он в конечном итоге фиксируется (после получения эксклюзивного права владения строкой кэша через MESI «чтение для владения»), он становится частью глобального общего порядка хранилищ, поскольку кэши являются согласованными.

См. также Может ли x86 переупорядочить узкий магазин с более широкой загрузкой, которая полностью его вместит? для непростого, но показательного случая.

Другие вопросы по теме