stringtranslate.com

Барьер памяти

В вычислительной технике барьер памяти , также известный как membar , memory fence или fence instructions , — это тип барьерной инструкции , который заставляет центральный процессор (ЦП) или компилятор применять ограничение упорядочения для операций памяти , выполненных до и после барьерной инструкции. Обычно это означает, что операции, выполненные до барьера, гарантированно будут выполнены до операций, выполненных после барьера.

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

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

Пример

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

Программа запускается через процесс, который может быть многопоточным (т. е. программный поток, такой как pthreads , в отличие от аппаратного потока). Различные процессы не разделяют пространство памяти, поэтому это обсуждение не применимо к двум программам, каждая из которых выполняется в отдельном процессе (следовательно, в разном пространстве памяти). Оно применимо к двум или более (программным) потокам, работающим в одном процессе (т. е. в одном пространстве памяти, где несколько программных потоков совместно используют одно пространство памяти). Несколько программных потоков в рамках одного процесса могут выполняться одновременно на многоядерном процессоре .

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

Первоначально ячейки памяти xи fоба содержат значение 0. Программный поток, работающий на процессоре № 1, выполняет цикл, пока значение fравно нулю, затем он выводит значение x. Программный поток, работающий на процессоре № 2, сохраняет значение 42в x, а затем сохраняет значение 1в f. Ниже показан псевдокод для двух фрагментов программы.

Шаги программы соответствуют отдельным инструкциям процессора.

В случае процессора PowerPC eioioинструкция обеспечивает, как ограждение памяти, что любые операции загрузки или сохранения, ранее инициированные процессором, полностью завершаются по отношению к основной памяти, прежде чем любые последующие операции загрузки или сохранения, инициированные процессором, получат доступ к основной памяти. [1] [2]

Тема №1 Ядро №1:

 while ( f == 0 ); // Здесь требуется ограничение памяти print x ;      

Тема №2 Ядро №2:

 x = 42 ; // Здесь требуется ограничение памяти f = 1 ;      

Можно было бы ожидать, что оператор печати всегда будет печатать число "42"; однако, если операции сохранения потока № 2 выполняются не по порядку, возможно, что fбудет обновлен до x , и оператор печати может, следовательно, вывести "0". Аналогично, операции загрузки потока № 1 могут выполняться не по порядку, и возможно, что xбудет прочитан до f проверяется, и снова оператор печати может, следовательно, вывести неожиданное значение. Для большинства программ ни одна из этих ситуаций не приемлема. Барьер памяти должен быть вставлен перед назначением потока № 2 значению , чтобы fгарантировать, что новое значение xбудет видимо другим процессорам во время или до изменения значения f. Другим важным моментом является то, что барьер памяти также должен быть вставлен перед доступом потока № 1 к значению , xчтобы гарантировать, что значение xне будет прочитано до того, как будет обнаружено изменение значения f.

Другой пример — когда водитель выполняет следующую последовательность:

 подготовить данные для аппаратного модуля // Здесь требуется ограничение памяти , чтобы запустить аппаратный модуль для обработки данных              

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

Другой наглядный пример (нетривиальный, возникающий на практике) см. в разделе « Двойная проверка блокировки» .

Многопоточное программирование и видимость памяти

Многопоточные программы обычно используют примитивы синхронизации, предоставляемые высокоуровневой средой программирования, такой как Java или .NET , или интерфейсом прикладного программирования (API), таким как POSIX Threads или Windows API . Примитивы синхронизации, такие как мьютексы и семафоры, предоставляются для синхронизации доступа к ресурсам из параллельных потоков выполнения. Эти примитивы обычно реализуются с барьерами памяти, необходимыми для обеспечения ожидаемой семантики видимости памяти . В таких средах явное использование барьеров памяти, как правило, не является необходимым.

Нестандартное выполнение и оптимизация переупорядочивания компилятора

Инструкции барьера памяти решают проблемы переупорядочивания только на аппаратном уровне. Компиляторы также могут переупорядочивать инструкции в рамках процесса оптимизации программы . Хотя эффекты на параллельное поведение программы могут быть схожими в обоих случаях, в целом необходимо принимать отдельные меры для подавления оптимизаций переупорядочивания компилятора для данных, которые могут совместно использоваться несколькими потоками выполнения.

В C и C++ ключевое слово volatile предназначалось для того, чтобы программы на C и C++ могли напрямую обращаться к отображенному в память вводу-выводу. Отображенный в память ввод-вывод обычно требует, чтобы чтение и запись, указанные в исходном коде, происходили в точном порядке, указанном без пропусков. Пропуски или переупорядочивание чтений и записей компилятором нарушили бы связь между программой и устройством, к которому осуществляется доступ с помощью отображенного в память ввода-вывода. Компиляторы AC или C++ не могут пропускать чтение и запись в ячейки памяти, а также не могут переупорядочивать чтение/запись относительно других подобных действий для той же ячейки памяти (переменной). Ключевое слово volatile не гарантирует барьер памяти для обеспечения согласованности кэша. Поэтому использование одного только volatile недостаточно для использования переменной для межпоточной связи на всех системах и процессорах. [3]

Стандарты C и C++ до C11 и C++11 не рассматривают несколько потоков (или несколько процессоров), [4] и, таким образом, полезность volatile зависит от компилятора и оборудования. Хотя volatile гарантирует, что volatile-чтения и volatile-записи будут происходить в точном порядке, указанном в исходном коде, компилятор может сгенерировать код (или ЦП может переупорядочить выполнение) таким образом, что volatile-чтение или запись будут переупорядочены относительно не volatile-чтений или записей, тем самым ограничивая его полезность в качестве межпоточного флага или мьютекса.

Смотрите также

Ссылки

  1. ^ Мэй, Кэти; Силха, Эд; Симпсон, Эйк; Уоррен, Хэнк (1993). Архитектура PowerPC: спецификация для нового семейства процессоров RISC . Издательство Morgan Kaufmann. стр. 350. ISBN 1-55860-316-6.
  2. ^ Кацмарчик, Кэри (1995). Оптимизация кода PowerPC . Addison-Wesley Publishing Company. стр. 188. ISBN 0-201-40839-2.
  3. ^ Корбет, Джонатан. «Почему класс типов „Volatile“ не следует использовать». Kernel.org . Получено 13 апреля 2023 г.
  4. ^ Boehm, Hans (июнь 2005 г.). Threads Cannot Be Implemented As a Library. Труды конференции ACM SIGPLAN 2005 г. по проектированию и реализации языков программирования . Ассоциация вычислительной техники . стр. 261. CiteSeerX 10.1.1.308.5939 . doi :10.1145/1065010.1065042. ISBN  1595930566.

Внешние ссылки