Протокол MESI & std :: atomi c - гарантирует, что все записи сразу видны другим потокам? - PullRequest
1 голос
/ 19 февраля 2020

Что касается std::atomic, то стандарт C ++ 11 гласит, что данные, хранящиеся в переменной atomi c, станут видимыми для загрузки этой переменной через «разумное количество времени».

От 29.3p13:

Реализации должны сделать хранилища Atomi c видимыми для атомов c В течение разумного промежутка времени.

Однако мне было любопытно узнать, что на самом деле происходит при работе с c архитектурами ЦП, основанными на протоколе когерентности кэша MESI (x86, x86-64, ARM и т. Д. c ..).

Если мое понимание протокола MESI правильное, ядро ​​всегда будет сразу считывать значение, ранее записанное / записываемое другим ядром, возможно, отслеживая его. (поскольку запись значения означает выдачу запроса RFO, который, в свою очередь, делает недействительными другие строки кэша)

Означает ли это, что, когда поток A сохраняет значение в std::atomic, другой поток B, который выполняет загрузку для этого Атоми c последовательно будет всегда наблюдать новое значение, написанное A на архитектурах MESI? (Предполагая, что никакие другие потоки не выполняют операции с этим атомом c)

Под «последовательно» я подразумеваю после того, как поток А выпустил хранилище атоми c. (Порядок модификации был обновлен)

Ответы [ 2 ]

3 голосов
/ 19 февраля 2020

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

MESI - это просто деталь реализации, которая ISO C ++ не о чем говорить. Гарантии, предоставляемые ISO C ++, касаются только порядка, а не фактического времени . ISO C ++ специально не указывается c, чтобы не предполагать, что он будет выполняться на "нормальном" процессоре. Реализация на некогерентной машине, которая требовала явных сбросов для видимости хранилища, теоретически возможна (хотя, вероятно, ужасна для выполнения операций release / acquis и seq-cst)

C ++ недостаточно определен c достаточно о сроках, чтобы даже разрешить реализацию в одноядерной кооперативной многозадачной системе (без упреждения), при этом компилятор иногда вставляет произвольные выходы. (Бесконечные циклы без какого-либо изменчивого доступа или ввода-вывода - UB). C ++ в системе, где на самом деле может выполняться одновременно только один поток, вполне нормально и возможно, при условии, что вы считаете временной интервал планировщика все еще «разумным» количеством времени. (Или меньше, если вы уступите или иным образом заблокируете.)

Даже модель формализма, используемая в ISO C ++ для предоставления гарантий относительно порядка, очень отличается от того, как аппаратные ISA определяют свои модели памяти. Формальные гарантии C ++ сугубо с точки зрения «происходит раньше» и синхронизируются с, а не «повторно» -заказывающими лакмусовыми тестами или чем-то подобным. Например, Как достичь барьера StoreLoad в C ++ 11? невозможно ответить за чистый формализм ISO C ++. И это показывает, насколько слабы гарантии C ++; например, существование полного порядка всех операций seq_cst не достаточно, чтобы подразумевать, что это происходит до того, как оно основано на этом, согласно формализму C ++. Но в реальной жизни этого достаточно для систем с когерентным кэшем и переупорядочением памяти только локальной (в пределах каждого ядра ЦП).

, когда поток A сохраняет значение в std::atomic

Это зависит от того, что вы подразумеваете под «созданием» хранилища.

Если вы имеете в виду фиксацию из буфера хранилища в кэш L1d, то да, это тот момент, когда хранилище становится на глобальном уровне, на обычной машине, которая использует MESI, чтобы дать всем ядрам процессора согласованное представление о памяти.

Хотя обратите внимание, что на некоторых ISA некоторые другие потоки могут видеть сохраняет до того, как они станут глобально видимыми через кеш . (т. е. аппаратная модель памяти не может быть «multi-copy atomi c» и допускает переупорядочение IRIW. POWER - единственный известный мне пример, который делает это в реальной жизни. См. Будет два атома c пишет в разные места в разных потоках всегда отображаются в одном и том же порядке другими потоками? для получения подробной информации о механизме HW: Пересылка хранилища для устаревших, также называемых градуированных хранилищ, между потоками SMT.)


Если вы имеете в виду выполнение локально, чтобы более поздние загрузки в этом потоке могли его видеть, то нет. std :: atomi c может использовать параметр memory_order более слабый, чем seq_cst.

Все У основных ISA есть правила упорядочения памяти, достаточно слабые, чтобы позволить буферу хранилища отделить выполнение команды от фиксации до кэша. Это также позволяет спекулятивно выполнять заказы вне очереди, предоставляя магазинам где-то приватное жилье после выполнения, прежде чем мы убедимся, что они были на правильном пути выполнения. (Хранилища не могут зафиксировать L1d до тех пор, пока инструкция хранилища не выйдет из вышедшей из строя части серверной части, и, следовательно, известно, что она не является спекулятивной.)

Если вы хотите дождаться чтобы ваш магазин был виден другим потокам перед выполнением последующих загрузок, используйте atomic_thread_fence(memory_order_seq_cst);. (Который на "нормальных" ISA со стандартным выбором C ++ -> сопоставлений asm будет компилировать полный барьер).

На большинстве ISA хранилище seq_cst (по умолчанию) также будет останавливать все последующие загрузки (и хранилища) в этом потоке, пока хранилище не станет глобально видимым. Но на AArch64 STLR является хранилищем с последовательным выпуском, и выполнение более поздних загрузок / хранилищ не должно останавливаться, пока / пока не будет выполнено выполнение LDAR (получение загрузки), пока STLR все еще находится в буфере хранения. Это реализует семантику S C настолько слабо, насколько это возможно, предполагая, что аппаратное обеспечение AArch64 действительно работает таким образом, а не просто рассматривает его как хранилище + полный барьер.

Но обратите внимание, что необходима только блокировка более поздних загрузок / хранилищ; вышедший из строя exe c инструкций ALU по регистрам все еще может продолжаться. Но если вы ожидаете какой-то эффект синхронизации из-за цепочек зависимостей операций FP, например, это не то, от чего вы можете зависеть в C ++.


Даже если вы используете seq_cst, ничего не происходит в эта ветка перед магазином видна другим, это еще не мгновенно. Задержка между ядрами на реальном оборудовании может составлять порядка , может быть, 40 нс на современных Intel x86, например. (Этот поток не должен так долго останавливаться на инструкции барьера памяти; иногда это происходит из-за отсутствия кэша в другом потоке, пытающемся прочитать строку, которая была аннулирована RFO этого ядра, чтобы получить исключительное право собственности.) Или, конечно, намного дешевле для логических ядер, которые совместно используют кэш L1d физического ядра: Каковы затраты на задержку и пропускную способность совместного использования производителем и потребителем расположения памяти между гипер-братьями и сестрами и не-гипер-братьями?

0 голосов
/ 19 февраля 2020

От 29.3p13:

Реализации должны сделать атомы c хранилищ видимыми для атомов c загрузок в разумные сроки.

C и стандарты C ++ повсеместно используются в потоках, поэтому их нельзя использовать в качестве формальных спецификаций. Они используют концепцию времени и в некоторой степени подразумевают, что все выполняется шаг за шагом, последовательно (если нет, у вас не будет звуковой программы semanti c), а затем говорят, что некоторые конструкции могут видеть эффекты не в порядке, без говоря что есть что.

Когда эффекты видны не по порядку, время нити плохо определено, так как у вас нет хронометра, который также будет не в порядке: вы не будете заниматься спортом с не по порядку выполнение действий!

Даже «не в порядке» предполагает, что некоторые вещи являются чисто последовательными, а некоторые другие операции могут быть «не в порядке» по отношению к первым. Это не то, как определяется std::atomic.

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

Так что, если две команды близки друг к другу в последовательном выполнении внутри потока, они также будут близки во время процессора. Разумный компилятор не должен перемещать энергозависимую операцию, вывод файла или операцию atomi c после очень дорогих «чистых» вычислений (без побочных эффектов, видимых извне).

Базовая c идея, которую многие члены комитета, к сожалению, не могли даже объяснить!

...