Что происходит, когда разные ядра ЦП записывают на один и тот же адрес ОЗУ без синхронизации?

Давайте предположим, что 2 ядра пытаются записать разные значения в один и тот же адрес ОЗУ (1 байт), в один и тот же момент времени (плюс-минус эта) и без использования каких-либо взаимосвязанных инструкций или барьеров памяти. Что происходит в этом случае и какое значение будет записано в основную память? Первый выигрывает? Последний побеждает? Неопределенное поведение?

2 ответа

Решение

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

Аппаратное обеспечение навязывает порядок (с помощью некоторого механизма, специфичного для реализации, разрывать связи в случае, если два запроса на владение поступают в один и тот же тактовый цикл от разных ядер). В большинстве современных процессоров x86 первое хранилище не будет записываться в ОЗУ, потому что есть общий кэш L3 с обратной записью для поглощения когерентного трафика без обращения к памяти.

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


(Я предполагаю, что мы говорим о нормальных (не NT) хранилищах с областями кешируемой памяти (WB, не USWC, UC или даже WT). Однако основная идея была бы одинаковой в любом случае; один магазин мог бы пойти во-первых, следующий шаг наступит на него. Данные из первого хранилища можно было бы временно наблюдать, если бы между ними происходила нагрузка в глобальном порядке, но в противном случае данные из хранилища, которые аппаратное обеспечение решило сделать 2-м, были бы длинными эффект

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


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

Этот материал "получения эксклюзивного доступа" выполняется с использованием (вариант) протокола MESI. Любая заданная строка в кэше может быть модифицированной (грязной), исключительной (принадлежит еще не записанной), общей (чистой копией; другие кэши также могут иметь копии, поэтому перед записью требуется RFO (чтение / запрос на владение)), или Недействительным. MESIF (Intel) / MOESI (AMD) добавляют дополнительные состояния для оптимизации протокола, но не меняют основополагающую логику, согласно которой только одно ядро ​​может одновременно изменять линию.

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

Когда хранилище выполняется, оно попадает в очередь хранилища. Он может перейти в L1D и стать глобально видимым в любое время после выхода на пенсию, но не раньше; Неиспользованные инструкции считаются спекулятивными, поэтому их архитектурные эффекты не должны быть видны вне ядра ЦП. Спекулятивные нагрузки не имеют архитектурного эффекта, только микроархитектура 1.

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

x86 имеет модель памяти с полным порядком хранения, где все ядра соблюдают один и тот же порядок даже для хранилищ с разными строками кэша (за исключением того, что они всегда видят свои собственные хранилища в программном порядке). Некоторые слабо упорядоченные архитектуры, такие как PowerPC, позволили бы некоторым ядрам видеть общий общий порядок от других ядер, но это переупорядочение может происходить только между магазинами к разным линиям. Всегда существует один порядок изменения для одной строки кэша. (Переупорядочение нагрузок по отношению друг к другу и другим хранилищам означает, что вы должны быть осторожны, наблюдая за вещами в слабо упорядоченном ISA, но существует один порядок изменений для строки кэша, навязанный MESI).

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

Кстати, современные процессоры x86 имеют аппаратный арбитраж для случая, когда несколько ядер борются за атомарное чтение-изменение-запись в одну и ту же строку кэша (и, таким образом, удерживают его в течение нескольких тактов, чтобы сделать lock add byte [rdi], 1 atomic), но обычные загрузки / хранилища должны иметь только строку кэша для одного цикла, чтобы выполнить загрузку или зафиксировать хранилище. Я думаю, что арбитраж для lock Инструкции ed - это другая вещь, от которой выигрывает ядро, когда несколько ядер пытаются зафиксировать хранилища в одной и той же строке кэша. Если вы не используете pause В инструкции, ядра предполагают, что другие ядра не модифицируют ту же самую строку кэша, и спекулятивно загружаются рано, и, таким образом, будут страдать от неправильной спекуляции с упорядочением памяти, если это произойдет. ( Каковы затраты времени ожидания и пропускной способности совместного использования производителем и потребителем места в памяти между гипер-братьями и сестрами по сравнению с не-гипер-братьями и сестрами?)

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


Примечания:

  1. Spectre размывает эту линию, используя атаку с таймированием кеша для считывания микроархитектурного состояния в архитектурное состояние.

Они будут в последовательности, вероятно, между кешами L1. Одна запись придет первой, а вторая - второй. Какой бы ни пришел второй, будет результат, который увидят последующие чтения.

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