Барьеры памяти не заставляют другие потоки видеть ваши хранилища любым быстрее. (За исключением того, что блокировка более поздних загрузок может немного снизить конкуренцию за фиксацию буферизованных хранилищ.)
Буфер хранилища всегда пытается зафиксировать списанные (известные неспекулятивные) хранилища в кэш L1d как можно быстрее. Кэш согласован 1, что делает их глобально видимыми благодаря MESI / MESIF / MOESI. буфер хранилища не предназначен в качестве правильного кеша или буфера объединения записи (хотя он может объединять последовательные хранилища в одну и ту же строку кеша), поэтому ему необходимо опустошить себя, чтобы освободить место для новых хранилищ. В отличие от кеша, он хочет оставаться пустым, а не полным.
Примечание 1: не только x86; все многоядерные системы любого ISA, в которых мы можем запускать один экземпляр Linux на его ядрах, обязательно имеют согласованный кэш; Linux полагается на volatile
в своих созданных вручную атомах, чтобы сделать данные видимыми. И аналогично, C ++ std::atomic
операции загрузки / сохранения с mo_relaxed
- это просто загрузка и сохранение asm на всех обычных процессорах, которые зависят от оборудования для видимости между ядрами, а не вручную. Когда использовать volatile с многопоточностью? объясняет th . Существуют кластеры или гибридные платы ARM микроконтроллер + DSP с некогерентной общей памятью, но мы не запускаем потоки одного и того же процесса в разных доменах когерентности. Вместо этого вы запускаете отдельный экземпляр ОС на каждом узле кластера. Я не знаю ни одной реализации C ++, где atomic<T>
load / store включают инструкции по ручному сбросу. (Пожалуйста, дайте мне знать, если они есть.)
Заборы / барьеры работают, заставляя текущий поток ждать
... до тех пор, пока необходимая видимость не будет достигнута с помощью обычных механизмов.
Простая реализация полного барьера (mfence
или lock
ed операция) заключается в остановке конвейера до тех пор, пока буфер хранилища не иссякнет, но высокопроизводительные реализации могут работать лучше и допускать выполнение вне очереди отдельно от ограничения порядка памяти.
(К сожалению, Skylake mfence
полностью блокирует -of-order выполнение, чтобы исправить неясную ошибку SKL079, связанную с загрузкой NT из памяти WC. Но lock add
или xchg
или что-то еще, только блокирует последующие загрузки после чтения L1d или буфера хранилища, пока барьер не достигнет конца буфера хранилища . И mfence
на более ранних процессорах, по-видимому, также не имеет этой проблемы.)
Как правило, на архитектурах, отличных от x86 (которые имеют явные инструкции asm для более слабых барьеров памяти, например только StoreStore ограждает, не заботясь о нагрузках), принцип тот же: блокируйте любые операции, которые необходимо блокировать, пока это ядро не завершит более ранние операции любого типа.
Связанный:
В конечном итоге вопрос, на который я пытаюсь ответить для себя, заключается в том, может ли поток 2 не видеть запись потока 1 в течение нескольких секунд.
Нет, в худшем случае задержка может быть чем-то вроде длины буфера хранилища (56 записей в Skylake, по сравнению с 42 в BDW), умноженное на задержку отсутствия кэша, потому что сильная модель памяти x86 (без переупорядочивания StoreStore) требует, чтобы хранилища фиксировались по порядку. Но RFO для нескольких строк кэша могут быть запущены одновременно, поэтому максимальная задержка может составлять 1/5 от этой (консервативная оценка: имеется 10 буферов заполнения строк). Также может быть конкуренция из-за нагрузок, которые также находятся в полете (или от других ядер), но нам просто нужен порядковый номер обратной стороны конверта.
Допустим, задержка RFO (DRAM или другое ядро) составляет 300 тактовых циклов (в основном составленных) на процессоре с тактовой частотой 3 ГГц. Таким образом, задержка наихудшего случая для того, чтобы хранилище стало глобально видимым, может быть примерно равной 300 * 56 / 5
= 3360 тактов ядра. Итак, с точностью до порядка наихудшего случая будет примерно ~ 1 микросекунда для ЦП с тактовой частотой 3 ГГц, как мы предполагаем. (Частота процессора не учитывается, поэтому оценка задержки RFO в наносекундах была бы более полезной).
Вот когда всем вашим магазинам нужно долго ждать RFO, потому что они все в местах, которые не кэшируются или принадлежат другим ядрам. И ни один из них не находится в одной и той же строке кеша подряд, поэтому ни один из них не может слиться в буфере хранилища. Так что обычно вы ожидаете, что это будет значительно быстрее.
Я не думаю, что существует какой-либо правдоподобный механизм, чтобы это заняло хотя бы сотню микросекунд, не говоря уже о целой секунде.
Если все ваши хранилища должны кэшировать строки, в которых все другие ядра борются за доступ к той же строке, ваши RFO могут занять больше времени, чем обычно, поэтому, возможно, десятки микросекунд, а может быть, даже сотня. Но такой худший случай случится не случайно.
person
Peter Cordes
schedule
11.07.2018