Я понимаю использование MemoryBarrier, но не то, что происходит за кулисами во время выполнения. Может ли кто-нибудь дать хорошее объяснение тому, что происходит?
Может ли кто-нибудь дать простое объяснение того, как "Full Fences" реализованы в .NET с помощью Threading.MemoryBarrier?
Ответ 1
В действительно сильной модели памяти, инструкции по искривлению забора не нужны. Все обращения к памяти будут выполняться по порядку, и все магазины будут отображаться во всем мире.
Необходимы блокировки памяти, потому что текущие общие архитектуры не обеспечивают сильную модель памяти - x86/x64 может, например, изменять порядок чтения по отношению к записи, (Более тщательный источник Руководство разработчика программного обеспечения для разработчиков Intel® 64 и IA-32, 8.2.2 Заказ памяти в P6 и более позднем процессоре Семьи" ). В качестве примера из gazillions Алгоритм Деккера не будет работать на x86/x64 без заборов.
Даже если JIT создает машинный код, в котором инструкции с загрузкой и хранением памяти аккуратно размещены, его усилия бесполезны, если ЦП затем переупорядочивает эти нагрузки и хранилища, что может, при условии сохранения иллюзии последовательной согласованности для текущего контекста/потока.
Риск слишком упрощения: он может помочь визуализировать нагрузки и хранилища в результате потока инструкций как громовое стадо диких животных. Когда они пересекают узкий мост (ваш процессор), вы никогда не сможете быть уверены в порядках животных, так как некоторые из них будут медленнее, быстрее, некоторые обгоняют, некоторые отстают. Если в начале - когда вы испускаете машинный код - вы разбиваете их на группы, устанавливая между ними бесконечно длинные ограждения, вы можете хотя бы убедиться, что группа A подходит к группе B.
Заборы обеспечивают упорядочение чтения и записи. Формулировка не является точной, но:
- забор магазина "ждет" для всех выдающихся операций хранения (записи) для завершения, но не влияет на нагрузки.
- загрузочный забор "ждет" для всех выдающихся операций загрузки (чтения) для завершения, но не влияет на магазины.
- полный забор "ждет" для завершения всех операций хранения и загрузки. Он имеет эффект, который читает и записывает до того, как забор будет выполнен до записи и загрузки, которые находятся на "другой стороне забора" (приходят позже, чем забор).
То, что JIT испускает для полного забора, зависит от архитектуры процессора (CPU) и того, какие гарантии памяти он обеспечивает. Поскольку JIT точно знает, на какой архитектуре он работает, он может выдавать правильные инструкции.
На моей машине x64 с .NET 4.0 RC это будет lock or
.
int a = 0;
00000000 sub rsp,28h
Thread.MemoryBarrier();
00000004 lock or dword ptr [rsp],0
Console.WriteLine(a);
00000009 mov ecx,1
0000000e call FFFFFFFFEFB45AB0
00000013 nop
00000014 add rsp,28h
00000018 ret
Руководство разработчика программного обеспечения для разработчиков Intel® 64 и IA-32 Глава 8.1.2:
-
"... заблокированные операции сериализуют все выдающиеся операции загрузки и хранения (то есть ждут их завершения)." ... "Заблокированные операции являются атомарными по отношению ко всем другим операциям памяти и всем внешне видимые события. Доступны только выбор команды и обращения к таблицам страниц заблокированные инструкции. Заблокированные инструкции могут использоваться для синхронизации данных, написанных один процессор и считывается другим процессором ".
-
Инструкции по упорядочению памяти адресованы этой конкретной потребности.
MFENCE
мог бы использоваться как полный барьер в вышеупомянутом случае (по крайней мере теоретически - для одного, заблокированные операции могут быть быстрее, для два из них могут привести к разному поведению).MFENCE
и его друзей можно найти в главе 8.2.5 "Усиление или ослабление модели памяти".
Есть еще несколько способов сериализации магазинов и загрузок, хотя они либо непрактичны, либо медленнее, чем приведенные выше методы:
-
В главе 8.3 вы найдете полные инструкции по сериализации, такие как
CPUID
. Эти сериализованные потоки команд также: "Ничто не может передавать инструкцию сериализации и инструкция сериализации не может передавать какую-либо другую инструкцию (чтение, запись, инструкция выборка или ввод-вывод). -
Если вы настроите память как сильную некэшированную (UC), она даст вам сильную модель памяти: никаких спекулятивных или выходы из-под контроля будут разрешены, и все обращения будут отображаться на шине, поэтому нет необходимости выдавать инструкцию.:) Конечно, это будет чуть медленнее, чем обычно.
...
Так что это зависит от. Если бы существовал компьютер с сильными гарантиями порядка, JIT, вероятно, ничего не испустил бы.
IA64 и другие архитектуры имеют свои собственные модели памяти - и, таким образом, гарантируют порядок памяти (или их отсутствие) - и их собственные инструкции/способы работы с хранением/загрузкой памяти.
Ответ 2
При выполнении параллельного программирования без блокировки следует заботиться о переупорядочении инструкций программы.
Переупорядочение программных команд может происходить на нескольких этапах:
- Оптимизация компилятора С#/VB.NET/F #
- Оптимизация компилятора JIT
- Оптимизация ЦП.
Запоминания памяти - единственный способ обеспечить конкретный порядок ваших программных инструкций. В принципе, забор памяти - это класс инструкций, который заставляет ЦП обеспечивать принудительное ограничение. Запонки памяти можно разделить на три категории:
- Загрузочные ограждения - убедитесь, что инструкции по загрузке нагрузки не перемещаются по заборам.
- Хранить ограждения - гарантировать, что инструкции ЦП хранилища не перемещаются через ограждения.
- Полные заборы - убедитесь, что загрузка или сохранение инструкций CPU не перемещаются по ограждениям.
В .NET Framework существует множество способов испускания ограждений: блокировка, монитор, ReaderWriterLockSlim и т.д.
Thread.MemoryBarrier испускает полный забор как на компиляторе JIT, так и на уровне процессора.