Может ли кто-нибудь дать простое объяснение того, как "Full Fences" реализованы в .NET с помощью Threading.MemoryBarrier?

Я понимаю использование 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, так и на уровне процессора.