Я знаю, что процессоры 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
) больше не существует, и с помощью «пока она не будет перезаписана более поздней записью» он охватывает любую более позднюю запись.
@PeterCordes Я не понимаю. Если 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.
См. также Может ли x86 переупорядочить узкий магазин с более широкой загрузкой, которая полностью его вместит? для непростого, но показательного случая. Но только для тех основ, о которых вы спрашиваете, только с одним местоположением, которые применимы практически к любому процессору, использующему когерентность кэша MESI, даже при расслабленном порядке памяти, таком как ARMv8.
@PeterCordes Спасибо, чувак! У меня один вопрос "вырванный из контекста". Поскольку я хочу изучить все эти вещи подробно, знаете ли вы какую-нибудь хорошую книгу, которую вы могли бы порекомендовать? Я думал о «Базовом руководстве по согласованности памяти и согласованности кэша», но я не уверен, поскольку в нем не упоминаются термины, которые вы использовали («переупорядочение StoreLoad», «перенаправление хранилища»). Заранее спасибо!
Отличной отправной точкой для переупорядочения памяти и таких терминов, как переупорядочение StoreLoad, являются preshing.com/20120710/… и preshing.com/20120913/acquire-and-release-semantics . Статья в Википедии MESI в порядке, но описание с точки зрения отслеживания общей шины вводит в заблуждение по сравнению с тем, что на самом деле делают современные процессоры («каталог» отслеживает, какая строка находится, чтобы знать, куда отправлять недействительные сообщения / RFO). Я узнал об этом не из книги, поэтому ИДК, хотя это хороший комп. арх. текст является хорошим фоном.
@PeterCordes И еще один вопрос. Это «официальный документ x86-TSO» cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf ? Я также нашел это ( cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.tphols.pdf), но, как я вижу, это всего лишь предложение (которое принято) и первая статья/ссылка это тот, который мне следует посмотреть/изучить (кстати, обе статьи написали одни и те же люди из Кембриджа).
Это формальное определение модели памяти совсем непросто понять. Это не очень хорошая отправная точка. Хотя, если еще раз просмотреть статью, можно увидеть немало текста, обсуждающего ее, так что, возможно, не так уж и плохо. Неформальное понимание основ того, как все работает, очень полезно для того, чтобы попытаться выяснить, что именно пытается определить формализм. Такие вещи, как документация по модели памяти ядра Linux, могут оказаться полезными (хотя переупорядочение во время компиляции является здесь серьезной проблемой, например, lwn.net/Articles/793253, в отличие от написания на asm и размышлений о процессорах).
Wiki тега x86 ссылки cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.pdf, «расширенная версия» этой статьи, 2009 года. Кажется, я видел что-то о обновленную версию этой статьи за последние пару лет, но сейчас я не могу ее найти. Давненько я на это не смотрел; Мне было достаточно понять это как программный порядок плюс буфер хранилища с переадресацией хранилища. (Наряду с тем, что IRIW невозможен, так как нет побочного канала, по которому некоторые ядра могли бы видеть хранилища других раньше других, как в POWER)
Во время поиска я нашел Research.swtch.com/hwmm , обзор x86-TSO в сравнении с более расслабленными моделями памяти (например, ARM). Re: IRIW, Будут ли две атомарные записи в разные места в разных потоках всегда отображаться в одном и том же порядке другими потоками? объясняет аппаратный механизм в POWER
То, о чем вы спрашиваете, описано в основах согласованности кэша 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 переупорядочить узкий магазин с более широкой загрузкой, которая полностью его вместит? для непростого, но показательного случая.
Связанный: Инструкции по загрузке Globally Invisible обсуждают странные эффекты, возникающие при пересылке в магазин. Ядро видит свои собственные хранилища до того, как они станут глобально видимыми; пока сохранение все еще находится в буфере хранилища, это последнее сохранение в этом месте из PoV этого ядра. Как только он в конечном итоге фиксируется (после получения эксклюзивного права владения строкой кэша через MESI «чтение для владения»), он становится частью глобального общего порядка хранилищ, поскольку кэши являются согласованными.