Параллельные магазины, видимые в последовательном порядке


руководство разработчика программного обеспечения Intel Architectures, Август. 2012, вып. 3А, разд. 8.2.2:

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

Но может ли это быть так?

Почему я спрашиваю это: рассмотрим двухъядерным процессором Intel i7 процессор с HyperThreading. В соответствии с руководством vol. 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, следовательно, состоит в том, чтобы ускорить нагрузки. Это Л3, который хранит скоростях. Это так ведь?

3 2

3 ответа:

Процессоры Intel (как и все обычные SMP-системы) используют (вариант) MESI для обеспечения когерентности кэша для кэшированных загрузок/хранилищ. то есть, что все ядра видят одно и то же представление памяти через свои кэши.

Ядро может выполнять запись в строку кэша только после выполнения операции Read For Ownership (RFO), переводя строку в исключительное состояние (ни один другой кэш не имеет допустимой копии строки, которая могла бы удовлетворить нагрузкам). Связанные: атомарные операции RMW не позволяют другим ядрам делать что-либо с целью кэш-строка путем блокировки ее в измененном состоянии на время выполнения операции.

Чтобы проверить этот вид переупорядочивания, вам нужны два других потока, которые оба читают оба хранилища (в противоположном порядке ). в предлагаемом сценарии одно ядро (reader2) считывает старое значение из памяти (или L3, или его собственное частное L2/L1) после того, как другое ядро (reader1) считало новое значение той же строки, сохраненной writer1. Это невозможно : для читателя 1 к см. хранилище writer1, writer1 должен уже выполнить RFO, который делает недействительными все остальные копии строки кэша в любом месте. А чтение непосредственно из DRAM без (эффективного) слежения за любыми кэшами обратной записи не допускается. (статья mesi Википедии имеет диаграммы.)

Когда хранилище фиксируется (из буфера хранилища внутри ядра) в кэш L1d, оно становится глобально видимым для всех других ядер одновременно. до этого только локальное ядро могло "видеть" его (через магазин->переадресация нагрузка от буферного хранилища).

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

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

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


Правилопоследовательного порядка является довольно слабым требованием. Многие не-x86 ISA не гарантируют этого на бумаге, но очень немногие реальные (не-x86) проекты процессоров имеют механизм, с помощью которого одно ядро может видеть данные из другого ядра, прежде чем оно станет глобально видимым для всех сердце. IBM POWER with SMT-один из таких примеров: будут ли две атомарные записи в разные места в разных потоках всегда рассматриваться другими потоками в одном и том же порядке? объясняет, как пересылка между логическими ядрами в пределах одного физического ядра может вызвать его. (Это похоже на то, что вы предложили, но в буфере магазина, а не в L2).

X86 микроархитектуры с гиперпоточностью (или SMT AMD в Ryzen) подчиняются этому требованию, статически разделяя буфер хранилища между логические ядра на одном физическом ядре. Что будет использоваться для обмена данными между потоками, выполняющимися на одном ядре с HT? Таким образом, даже в пределах одного физического ядра хранилище должно зафиксироваться в L1d (и стать глобально видимым) до того, как другое логическое ядро сможет загрузить новые данные.

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

(другие требования модели памяти TSO x86, такие как загрузка и хранение данных в программном порядке, сложнее. Современные процессоры x86 выполняются не по порядку, но используют буфер порядка памяти для поддержания иллюзии и имеют фиксацию хранилищ на L1d в программном порядке. Нагрузки могут спекулятивно принимать значения раньше, чем они "предполагаются", а затем проверять позже. Вот почему процессоры Intel имеют конвейер "mis-спекуляции порядка памяти": каковы затраты на задержку и пропускную способность совместное использование производителем и потребителем места памяти между гипер-братьями и сестрами по сравнению с негипер-братьями?.)

Как указывает @BeeOnRope, существуетвзаимодействие между HT и поддержанием иллюзии отсутствия переупорядочения нагрузки : обычно процессор может обнаружить, когда другое ядро коснулось строки кэша после фактического чтения нагрузки, но до того, как ему было разрешено прочитать ее: порт загрузки может отслеживать недействительность этой строки кэша. Но с HT, порты нагрузки также нужно вынюхивать хранилища, которые другой hyperthread фиксирует в кэше L1d, потому что они не сделают недействительной строку. (Возможны и другие механизмы, но это проблема, которую проектировщики процессоров должны решить, если они хотят высокую производительность для "нормальных" нагрузок.)


Сноска 1 : на слабо упорядоченном ISA вы бы использовали барьеры упорядочения нагрузки для управления порядком, в котором 2 загрузки в каждом читателе берут свои данные из глобально когерентного домена кэша.

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

X86 lfence в принципе не имеет вариантов использования упорядочения памяти, и sfence только полезно для магазинов NT. Только mfence полезно для "обычных" вещей, когда один поток пишет что-то, а затем читает другое место. http://preshing.com/20120515/memory-reordering-caught-in-the-act/. Таким образом, он блокирует переупорядочивание и пересылку хранилища через барьер.


В свете ответа @ Benoit, следующий вопрос: единственная цель L1 и L2, следовательно, состоит в том, чтобы ускорить нагрузки. Это Л3, который хранит скоростях. Это правда?

Нет, L1d и L2 - это кэш обратной записи: какой метод отображения кэша используется в процессоре intel core i7?. Повторные магазины в одной и той же строке могут быть поглощены L1d.

Но Intel использует инклюзивные кэш-файлы L3, так как L1d в одном ядре может иметь единственную копию? L3 на самом делеtag -inclusive, что все, что нужно для работы тегов L3 в качестве фильтра snoop (вместо широковещательных запросов RFO на каждое ядро). Фактические данные в грязных строках являются частными для внутренних кэшей каждого ядра, но L3 знает, какое ядро имеет текущие данные для строки (и, следовательно, куда отправить запрос, когда другое ядро хочет прочитать строку, которую другое ядро имеет в измененном состоянии). Чистые строки кэша (в общем состоянии) включают в себя данные L3, но запись в строку кэша не приводит к записи в L3.

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

Таким образом, другие процессоры всегда будут видеть только один из следующих результатов при чтении этого места памяти:

  • Значение перед любой записью (т. е. чтение предшествовало обеим записям)

  • Значение после процессора 0 записывается (то есть как если бы процессор 2 писал сначала, а потом процессор 0 overwrote)

  • Значение после записи процессора 2 (т. е. как если бы процессор 0 сначала написал, а затем процессор 2 перезаписал)

Процессор 1 не сможет увидеть значение после записи процессора 0, но в то же время процессор 3 увидит значение после записи процессора 2 (или наоборот).

Имейте в виду, что поскольку внутрипроцессорная переупорядоченность разрешена (см. раздел 8.2.3.5), процессоры 0 и 2 могут видеть вещи по-разному.

Ой, это сложный вопрос! Но я постараюсь...

Записи идут не глубже L2

В принципе это невозможно, так как Intel использует инклюзивные кэши. Любые данные, записанные в L1, также будут иметь место в L2 и L3, если вы не предотвратите кэширование, отключив их через CR0/MTRR.

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