Ответ 1
Похоже, что атомные операции в памяти будут выполняться непосредственно в памяти (ОЗУ).
Нет, до тех пор, пока все возможные наблюдатели в системе воспринимают операцию как атомарную, операция может включать только кеш. Удовлетворение этого требования намного сложнее для операций атомарного чтения-модификации-записи (например, lock add [mem], eax
, особенно с несогласованным адресом), то есть когда ЦП может утверждать сигнал LOCK #. Вы все еще не увидите больше, чем в asm: аппаратное обеспечение реализует требуемую ISA семантику для инструкций lock
ed.
Хотя я сомневаюсь, что на современных процессорах, где контроллер памяти встроен в CPU, есть физический внешний вывод LOCK #, а не отдельный чип северного моста.
std::atomic<int> X; X.load()
ставит только "лишнее" значение.
Составители не загружают MFENCE для seq_cst. Я думаю, что я читал, что MSVC действительно испускал MFENCE для этого (возможно, чтобы не переупорядочивать с неохраняемыми магазинами NT?), Но это уже не так: я только что протестировал MSVC 19.00.23026.0. Ищите foo и bar в asm-выходе из эта программа, которая выгружает свой собственный asm в онлайн-компиляторе и рабочем сайте.
Я думаю, что причина, по которой мы не нуждаемся в заборе, заключается в том, что модель памяти x86 запрещает переупорядочивание LoadStore и LoadLoad. Раньше (non seq_cst) хранилища все еще можно отложить до загрузки seq_cst, поэтому он отличается от использования автономного std::atomic_thread_fence(mo_seq_cst);
до X.load(mo_acquire);
Если я правильно понимаю,
X.store(2)
простоmov [somewhere], 2
Нет, магазины seq_cst требуют полной инструкции по защите памяти, чтобы запретить переупорядочение StoreLoad, которое в противном случае могло бы случиться.
MSVC asm для магазинов совпадает с clang's, используя xchg
, чтобы сделать хранилище и барьер памяти с той же инструкцией. (На некоторых процессорах, особенно AMD, инструкция lock
ed в качестве барьера может быть дешевле, чем MFENCE, потому что IIRC AMD документирует дополнительную семантику сериализации-конвейера (для выполнения команд, а не только для упорядочения памяти) для MFENCE).
Этот вопрос выглядит как часть 2 вашей предыдущей модели памяти в С++: последовательная согласованность и атомарность, где вы спросили:
Как ЦПУ реализует атомарные операции внутри?
Как вы отметили в вопросе, атомарность не связана с упорядочением по отношению к любым другим операциям. (т.е. memory_order_relaxed
). Это просто означает, что операция выполняется как одна неделимая операция, отсюда имя, а не как несколько частей, которые могут произойти частично до и частично после чего-то иначе.
Вы получаете атомарность "бесплатно" без дополнительного оборудования для согласованных нагрузок или сохраняете до размера путей передачи данных между ядрами, памятью и шинами ввода-вывода, такими как PCIe., т.е. между различные уровни кеша и между кэшами отдельных ядер. Контроллеры памяти являются частью процессора в современных конструкциях, поэтому даже устройство PCIe, использующее память, должно пройти через системный агент ЦП. (Это даже позволяет Skylake eDRAM L4 (недоступно ни в одном настольном процессоре:() работает как кеш памяти (в отличие от Broadwell, который использовал его в качестве кэша-жертвы для L3 IIRC), сидящего между памятью и всем остальным в системе, поэтому он может даже кэшировать DMA).
Это означает, что аппаратное обеспечение ЦП может делать все, что необходимо для обеспечения того, чтобы хранилище или загрузка были атомарными по отношению к чему-либо еще в системе, которые могут его наблюдать. Наверное, это не так много. DDR-память использует достаточно широкую шину данных, которая в 64-битном выровненном хранилище действительно электрически переводит шину памяти в DRAM все в одном цикле. (забавный факт, но неважный). Протокол последовательной шины, такой как PCIe, не помешает ему быть атомарным, если одно сообщение достаточно велико. И поскольку контроллер памяти - это единственное, что можно напрямую связать с DRAM, неважно, что он делает внутренне, просто размер передачи между ним и остальной частью ЦП). Но в любом случае это "бесплатная" часть: для сохранения атомного переноса атома не требуется временная блокировка других запросов.
x86 гарантирует, что согласованные нагрузки и хранилища до 64 бит являются атомарными, но не более широкий доступ. Маломощные реализации могут разбить векторные нагрузки/хранилища на 64-битные куски, такие как P6, с PIII до Pentium M.
Атомные операторы выполняются в кеше
Помните, что атомный просто означает, что все наблюдатели считают, что это произошло или не произошло, никогда не произошло частично. Там нет требования, чтобы он фактически дошел до основной памяти сразу (или вообще, если перезаписано в ближайшее время). Атомно модифицировать или читать кеш L1 достаточно, чтобы гарантировать, что любой другой ядро или доступ к DMA будет видеть выравниваемое хранилище или загрузка, выполняются как одна атомная операция. Хорошо, если эта модификация происходит задолго после того, как хранилище выполняется ( например, задерживается исполнением вне порядка до закрытия магазина).
Современные процессоры, такие как Core2 со 128-битными путями повсюду, как правило, имеют атомные SSE 128b нагрузки/хранилища, выходящие за пределы того, что гарантирует x86 ISA. Но обратите внимание на интересное исключение на многопроцессорном Opteron, вероятно, из-за гипертранспорта. Это доказательство того, что атомарно модифицирующий кеш L1 недостаточно, чтобы обеспечить атомарность для магазинов, более широких, чем самые узкие путь данных (в этом случае это не путь между кешем L1 и исполнительными блоками).
Важное значение выравнивания: загрузка или хранение, пересекающая границу строки кэша, должна выполняться в двух отдельных доступах. Это делает его неатомным.
x86 гарантирует, что кешированные обращения до 8 байтов являются атомарными, если они не пересекают границу 8B на AMD/Intel. (Или для Intel только на P6 и более поздних версиях, не пересекайте границу линии кэша). Это означает, что целые строки кэша (64B на современных процессорах) передаются атомарно на Intel, хотя это шире, чем пути данных (32B между L2 и L3 на Haswell/Skylake). Эта атомарность не является полностью "свободной" в аппаратном обеспечении и, возможно, требует некоторой дополнительной логики, чтобы предотвратить загрузку от чтения строки кэша, которая только частично передана. Хотя передача в кеш-строку происходит только после того, как старая версия была признана недействительной, так что ядро не должно читать из старой копии, пока происходит передача. AMD может повредить на практике меньшие границы, возможно, из-за использования другого расширения для MESI, которое может передавать грязные данные между кешами.
Для более широких операндов, таких как атомарная запись новых данных в несколько записей структуры, вам необходимо защитить ее с помощью блокировки, к которой все обращаются к ней. (Вы можете использовать x86 lock cmpxchg16b
с циклом повтора для создания атомного 16b-хранилища. Обратите внимание, что нет способа эмулировать его без мьютекса.)
Atomic read-modify-write - это то, где становится сложнее
related: мой ответ на Может ли num ++ быть атомарным для 'int num'? подробнее об этом.
Каждое ядро имеет частный кеш L1, который является когерентным со всеми другими ядрами (с использованием протокола MOESI). Кэш-линии передаются между уровнями кеша и основной память в кусках размером от 64 бит до 256 бит. (эти передачи могут фактически быть атомарными по всей детализации в кеш-строке?)
Чтобы сделать атомный RMW, ядро может поддерживать линию кэша L1 в измененном состоянии без принятия каких-либо внешних изменений в затронутой строке кэша между загрузкой и хранилищем, остальная часть системы увидит, что операция является атомной. (И, следовательно, он является атомарным, поскольку обычные правила выполнения вне порядка требуют, чтобы локальный поток видел, что его собственный код работает в порядке выполнения программы.)
Он может сделать это, не обрабатывая сообщения о кеш-когерентности, когда атомный RMW находится в полете (или более сложная версия, которая позволяет больше parallelism для других операционных систем).
Unaligned lock
ed ops - проблема: нам нужны другие ядра, чтобы увидеть, что изменения в двух строках кэша происходят как одна атомная операция. Это может потребовать фактического хранения DRAM и блокировки шины. (Руководство по оптимизации AMD говорит, что это то, что происходит на их процессорах, когда недостаточно блокировки кеша.)