Параллельные магазины рассматриваются в последовательном порядке
Руководство разработчика программного обеспечения Intel Architectures, август 2012 г., вып. 3А, раздел 8.2.2:
Любые два хранилища рассматриваются в согласованном порядке процессорами, отличными от тех, которые выполняют хранилища.
Но может ли это быть так?
Причина, по которой я спрашиваю, такова: рассмотрим двухъядерный процессор Intel i7 с HyperThreading. Согласно Руководству, вып. 1, рис. 2-8, логические процессоры i7 0 и 1 совместно используют кэш L1/L2, но его логические процессоры 2 и 3 используют другой кэш L1/L2 - тогда как все логические процессоры совместно используют один кэш L3. Предположим, что логические процессоры 0 и 2, которые не совместно используют кэш-память L1/L2, выполняют запись в одну и ту же ячейку памяти примерно в одно и то же время, и что записи на данный момент идут не глубже, чем L2. Не могли ли логические процессоры 1 и 3 (которые являются "процессорами, отличными от тех, которые выполняют хранилища") затем увидеть "два хранилища в несогласованном порядке"?
Чтобы достичь согласованности, не должны ли логические процессоры 0 и 2 выдавать инструкции SFENCE, а логические процессоры 1 и 3 выдают инструкции LFENCE? Несмотря на это, Руководство, похоже, считает иначе, и его мнение по этому вопросу не выглядит просто опечаткой. Это выглядит преднамеренно. Я не совсем понимаю.
ОБНОВИТЬ
В свете ответа @Benoit, следующий вопрос: Поэтому единственной целью L1 и L2 является ускорение нагрузки. Это L3, который ускоряет магазины. Это правильно?
3 ответа
Процессоры Intel (как и все обычные SMP-системы) используют (вариант) MESI для обеспечения согласованности кэша для кэшированных загрузок / хранилищ. то есть, что все ядра видят одно и то же представление о памяти через свои кэши.
Ядро может выполнять запись в строку кэша только после выполнения Read For Ownership (RFO), получая строку в состоянии Exclusive (никакие другие кэши не имеют действительной копии строки, которая могла бы удовлетворить нагрузки). Связанный: атомарные операции RMW препятствуют тому, чтобы другие ядра сделали что-либо к целевой строке кэша , блокируя это в Измененном состоянии на время операции.
Чтобы проверить этот тип переупорядочения, вам нужны два других потока, которые оба читают оба хранилища ( в обратном порядке). В вашем предлагаемом сценарии одно ядро (reader2) считывает старое значение из памяти (или L3, или его собственный закрытый L2/L1) после того, как другое ядро (reader1) прочитало новое значение той же строки, сохраненной writer1. Это невозможно: чтобы reader1 мог видеть хранилище writer1, writer1, должно быть, уже выполнил RFO, который делает недействительными все остальные копии строки кэша в любом месте. И чтение непосредственно из DRAM без (эффективно) отслеживания любых кэшей обратной записи не допускается. ( В статье MESI Википедии есть диаграммы.)
Когда хранилище фиксирует (из буфера хранилища внутри ядра) к L1d-кешу, оно становится глобально видимым для всех других ядер одновременно. До этого только локальное ядро могло "видеть" его (через store-> forwarding из буфера хранилища).
В системе, где единственным способом распространения данных из одного ядра в другое является глобальная область когерентности кэша, только когерентность кэша MESI гарантирует существование единого глобального порядка хранения, с которым могут согласиться все потоки. Благодаря строгим правилам упорядочения памяти в x86 этот глобальный порядок хранения представляет собой некоторое чередование порядка программ, и мы называем это моделью памяти Total Store Order.
Модель сильной памяти x86 не допускает переупорядочения LoadLoad, поэтому загрузки берут свои данные из кэша в программном порядке без каких-либо барьерных инструкций в потоках считывателя. 1
На самом деле загрузки отслеживают буфер локального хранилища, прежде чем получать данные из связного кэша. По этой причине приведенное вами правило согласованного заказа исключает случай, когда любое хранилище было выполнено тем же ядром, которое выполняет загрузку. См. Глобальные невидимые инструкции по загрузке для получения дополнительной информации о том, откуда на самом деле поступают данные загрузки. Но когда адреса загрузки не пересекаются с какими-либо недавними хранилищами, применяется то, что я сказал выше: порядок загрузки - это порядок выборки из общего глобально согласованного домена кэша.
Правило последовательного порядка - довольно слабое требование. Многие ISA не x86 не гарантируют это на бумаге, но очень немногие фактические (не x86) процессоры имеют механизм, с помощью которого одно ядро может видеть данные хранилища из другого ядра, прежде чем оно станет глобально видимым для всех ядер. IBM POWER с SMT является одним из таких примеров: будут ли две атомные записи в разные места в разных потоках всегда рассматриваться в одном и том же порядке другими потоками? объясняет, как это может быть вызвано пересылкой между логическими ядрами в пределах одного физического ядра. (Это похоже на то, что вы предложили, но в буфере хранилища, а не в L2).
Микроархитектуры x86 с HyperThreading (или SMT AMD в Ryzen) выполняют это требование, статически распределяя буфер хранилища между логическими ядрами на одном физическом ядре. Что будет использоваться для обмена данными между потоками, выполняющимися на одном ядре с HT? Таким образом, даже в пределах одного физического ядра хранилище должно зафиксировать L1d (и стать глобально видимым), прежде чем другое логическое ядро сможет загрузить новые данные.
Вероятно, проще не иметь пересылку из удаленных, но не зафиксированных хранилищ в одном логическом ядре в другие логические ядра в том же физическом ядре.
(Другие требования к модели памяти TSO в x86, такие как загрузка и сохранение в порядке программы, сложнее. Современные процессоры x86 работают не по порядку, но используют буфер порядка памяти, чтобы поддерживать иллюзию, и хранилища фиксируют L1d в порядке программы. Загрузки могут спекулятивно принимать значения раньше, чем они "должны", а затем проверять позже. Вот почему ЦП Intel имеют конвейерные нюансы "неправильного спекуляции порядка памяти": каковы затраты на задержку и пропускную способность разделения между производителями и потребителями? область памяти между гипер-братьями и сестрами и не гипер-братьями?)
Как указывает @BeeOnRope, между HT и поддержанием иллюзии отсутствия переупорядочения LoadLoad есть взаимодействие: обычно ЦП может обнаружить, когда другое ядро коснулось строки кэша после фактического чтения загрузки, но до того, как ему было разрешено архитектурно читать ее: порт загрузки может отслеживать недействительность этой строки кэша. Но с HT, порты загрузки также должны отслеживать хранилища, которые другая гиперпотока фиксирует в кеше L1d, потому что они не аннулируют строку. (Возможны и другие механизмы, но это проблема, которую разработчики ЦП должны решить, если им нужна высокая производительность для "обычных" нагрузок.)
Сноска 1. На слабо упорядоченном ISA вы должны использовать барьеры для упорядочения нагрузки, чтобы управлять порядком, в котором 2 нагрузки в каждом считывателе берут свои данные из глобально согласованной области кэша.
Писатели пишут только по одному магазину, так что ограждение не имеет смысла. Поскольку все ядра совместно используют один домен когерентного кэша, ограждения должны управлять только локальным переупорядочением внутри ядра. Буфер хранилища в каждом ядре уже пытается сделать хранилища глобально видимыми как можно быстрее (при соблюдении правил упорядочения ISA), поэтому барьер просто заставляет процессор ждать перед выполнением последующих операций.
x86 lfence
в основном не имеет вариантов использования упорядочения памяти, и sfence
полезно только в магазинах NT. Только mfence
полезно для "нормальных" вещей, когда один поток что-то пишет, а затем читает другое место. http://preshing.com/20120515/memory-reordering-caught-in-the-act/. Таким образом, он блокирует переупорядочение и пересылку StoreLoad через барьер.
В свете ответа @Benoit, следующий вопрос: Поэтому единственной целью L1 и L2 является ускорение нагрузки. Это L3, который ускоряет магазины. Это правильно?
Нет, L1d и L2 - это кэши с обратной записью. Какой метод отображения кэша используется в процессоре Intel Core i7?, Повторные накопления в одну и ту же линию могут быть поглощены L1d.
Но Intel использует инклюзивные кеши L3, так как L1d в одном ядре может иметь единственную копию? L3 на самом деле включает теги, и это все, что нужно для работы тегов L3 в качестве фильтра отслеживания (вместо того, чтобы транслировать запросы RFO на каждое ядро). Фактические данные в грязных строках являются частными для внутренних кэшей каждого ядра, но L3 знает, какое ядро имеет текущие данные для строки (и, следовательно, куда отправлять запрос, когда другое ядро хочет прочитать строку, которую другое ядро имеет в Модифицированном государство). Чистые строки кэша (в состоянии Shared) включают данные L3, но запись в строку кэша не обеспечивает сквозную запись в L3.
Я полагаю, что документация Intel говорит о том, что механика чипа x86 гарантирует, что другие процессоры всегда будут видеть записи в согласованном порядке.
Таким образом, другие процессоры увидят только один из следующих результатов при чтении этой области памяти:
значение перед любой записью (т.е. чтение предшествовало обеим записям)
значение после записи процессора 0 (т. е. как будто процессор 2 сначала записал, а затем процессор 0 перезаписал)
значение после записи процессора 2 (т. е. как будто процессор 0 сначала записал, а затем процессор 2 перезаписал)
Процессор 1 не сможет увидеть значение после записи процессора 0, но в то же время процессор 3 увидит значение после записи процессора 2 (или наоборот).
Имейте в виду, что, поскольку внутрипроцессорное переупорядочение разрешено (см. Раздел 8.2.3.5), процессорные 0 и 2 могут видеть вещи по-разному.
Ой, это сложный вопрос! Но я попытаюсь...
записи идут не глубже, чем L2
В принципе это невозможно, поскольку Intel использует инклюзивные кэши. Любые данные, записанные в L1, также будут иметь место в L2 и L3, если вы не запретите кэширование, отключив их через CR0/MTRR.
При этом, я полагаю, существуют механизмы арбитража: процессоры выдают запрос на запись данных, и арбитр выбирает, какой запрос предоставляется из числа ожидающих запросов из каждой очереди запросов. Выбранные запросы транслируются шпионам, а затем кэшам. Я предполагаю, что это предотвратило бы гонку, обеспечив согласованный порядок, видимый процессорами, отличными от того, который выполняет запрос.