Может ли кто-нибудь дать простое объяснение того, как «Полные заборы» реализованы в .Net с использованием Threading.MemoryBarrier? - PullRequest
16 голосов
/ 22 марта 2010

Мне понятно использование MemoryBarrier, но не то, что происходит за кулисами во время выполнения. Кто-нибудь может дать хорошее объяснение того, что происходит?

Ответы [ 2 ]

16 голосов
/ 22 марта 2010

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

Необходимы заборы памяти, потому что современные распространенные архитектуры не обеспечивают надежную модель памяти - 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. Они также сериализуют поток команд: "Ничто не может передать команду сериализации и инструкция сериализации не может передавать никакую другую инструкцию (чтение, запись, инструкция fetch или I / O) ".

  • Если вы установите память как сильную некэшированную (UC), это даст вам сильную модель памяти : никакие спекулятивные или неупорядоченные обращения не будут разрешены, и все обращения появятся на шине поэтому нет необходимости издавать инструкцию. :) Конечно, это будет немного медленнее, чем обычно.

...

Так что это зависит от. Если бы существовал компьютер с сильными гарантиями заказа, JIT, вероятно, ничего бы не испустил.

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

4 голосов
/ 22 марта 2010

При параллельном программировании без блокировок следует позаботиться о переупорядочении инструкций программы.

Изменение порядка команд в программе может происходить в несколько этапов:

  1. Оптимизация компилятора C # / VB.NET / F #
  2. Оптимизация JIT-компилятора
  3. Оптимизация процессора.

Ограждения памяти - это единственный способ обеспечить определенный порядок инструкций вашей программы. По сути, ограничение памяти - это класс инструкций, который заставляет ЦП обеспечивать ограничение порядка. Ограждения памяти можно разделить на три категории:

  1. Загрузка заборов - убедитесь, что инструкции по загрузке процессора не перемещаются по заборам
  2. Заборы магазина - убедитесь, что инструкции ЦП магазина не пересекают заборы
  3. Полные заборы - убедитесь, что инструкции по загрузке или хранению ЦП не пересекают заборы

В .NET Framework существует множество способов создания заборов: блокировка, монитор, ReaderWriterLockSlim и т. Д.

Thread.MemoryBarrier создает полную границу как на уровне JIT-компилятора, так и на уровне процессора.

...