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

Из Википедии, бесплатной энциклопедии

Барьер памяти (англ. memory barrier, membar, memory fence, fence instruction) — вид барьерной инструкции, которая приказывает компилятору (при генерации инструкций) и центральному процессору (при исполнении инструкций) устанавливать строгую последовательность между обращениями к памяти до и после барьера. Это означает, что все обращения к памяти перед барьером будут гарантированно выполнены до первого обращения к памяти после барьера.

Барьеры памяти необходимы, так как большинство современных процессоров использует оптимизации производительности, которые могут привести к переупорядочиванию инструкций. Также переупорядочивание обращений к памяти может быть вызвано компилятором в процессе оптимизации использования регистров целевого процессора. Такие перестановки обычно не влияют на корректность программы с одним потоком исполнения, но могут вызвать непредсказуемое поведение в многопоточных программах. Правила изменения порядка исполнения инструкций зависят от архитектуры. Некоторые архитектуры предоставляют несколько типов барьеров с различными гарантиями. Например, amd64 предоставляет следующие инструкции: SFENCE (англ. store fence), LFENCE(англ. load fence), MFENCE(англ. memory fence)[1]. Intel Itanium обеспечивает отдельные «запоминающие» (англ. acquire) и «освобождающие» (англ. release) барьеры памяти, которые учитывают видимость операций чтения после записи с точки зрения читателя и писателя соответственно.

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

Пример[править | править код]

Следующая программа исполняется на двух процессорах.

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

Процессор #1:

 while (f == 0) { }  // Здесь необходим барьер  print x; 

Процессор #2:

 x = 42;  // Здесь необходим барьер  f = 1; 

Хотя ожидается, что print всегда напечатает «42», но если процессор #2 изменит порядок исполнения инструкций и вначале изменит значение f, то print может вывести «0». Аналогично, процессор #1 может прочитать x перед f, и print снова выведет не ожидаемое значение. Для большинства программ ни одна из этих ситуаций не приемлема. Барьер памяти для процессора #2 может быть вставлен перед изменением значения f. Также можно вставить барьер для процессора #1 перед чтением x[2].

Оптимизации порядка исполнения компилятором[править | править код]

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

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

Примечания[править | править код]

  1. peeterjoot. Intel memory ordering, fence instructions, and atomic operations (4 сентября 2009). Дата обращения: 1 октября 2017. Архивировано 2 октября 2017 года.
  2. Другие примеры — в статье о блокировке с двойной проверкой
  3. Volatile Considered Harmful — Linux Kernel Documentation. Дата обращения: 1 октября 2017. Архивировано 4 октября 2017 года.