Барьер памяти (>gj,yj hgbxmn)
Барьер памяти (англ. 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]
Примечания
[править | править код]- ↑ peeterjoot. Intel memory ordering, fence instructions, and atomic operations (4 сентября 2009). Дата обращения: 1 октября 2017. Архивировано 2 октября 2017 года.
- ↑ Другие примеры — в статье о блокировке с двойной проверкой
- ↑ Volatile Considered Harmful — Linux Kernel Documentation . Дата обращения: 1 октября 2017. Архивировано 4 октября 2017 года.