Что действительно делает барьер LoadLoad?
В Java, когда у нас есть два потока, которые используют следующие переменные:
int a;
volatile int b;
если поток 1:
a = 5;
b = 6;
Затем между этими двумя инструкциями вставлен барьер StoreStore, а "a" сбрасывается обратно в основную память.
Теперь, если поток 2:
if(b == 6)
a++;
барьер LoadLoad вставлен между ними, и мы гарантируем, что если новое значение "b" будет видимым, тогда также будет видно новое значение "a". Но как на самом деле это достигается? Загружает ли LoadLoad кэширование/регистры процессора? Или просто инструктирует CPU для получения значений переменных, которые следуют за чтением из volatile снова из CPU?
Я нашел эту информацию о барьерах LoadLoad (http://gee.cs.oswego.edu/dl/jmm/cookbook.html):
Барьеры LoadLoad Последовательность: Load1; LoadLoad; Load2 гарантирует, что Данные Load1 загружаются до доступа к данным Load2 и всех загружаются последующие инструкции по загрузке. В общем, явный LoadLoad необходимы барьеры для процессоров, выполняющих спекулятивные нагрузки и/или обработки вне очереди, в которой инструкции ожидающей нагрузки могут байпасные магазины ожидания. О процессорах, гарантирующих всегда сохранение порядок загрузки, барьеры равны нулю.
но на самом деле это не объясняет, как это достигается.
Ответы
Ответ 1
Я приведу один пример о том, как это достигается. Вы можете подробнее прочитать подробности здесь. Для x86-процессоров, как вы указали, LoadLoad заканчивается без операций. В статье, которую я связал, Марк указывает, что
Doug перечисляет StoreStore, LoadLoad и LoadStore
Таким образом, по существу единственным необходимым барьером является архитектура StoreLoad для x86. Итак, как это достигается на низком уровне?
Это выдержка из блога:
Здесь код, который он сгенерировал как для энергозависимых, так и для нестабильных значений:
nop ;*synchronization entry
mov 0x10(%rsi),%rax ;*getfield x
И для летучих записей:
xchg %ax,%ax
movq $0xab,0x10(%rbx)
lock addl $0x0,(%rsp) ;*putfield x
Инструкция lock
- это StoreLoad, как указано в кулинарной книге Дуга. Но инструкция блокировки также синхронизирует все чтения с другими процессами как в списке
Заблокированные инструкции могут использоваться для синхронизации данных, написанных одним процессор и считывается другим процессором.
Это уменьшает накладные расходы при выдаче барьеров LoadLoad LoadStore для летучих нагрузок.
Все сказанное, я повторю то, что заметили ассирийцы. То, как это происходит, не должно быть важно для разработчика (если вы заинтересованы в реализации процессора/компилятора, это еще одна история). Ключевое слово volatile
- это вид интерфейса, говорящего
- Вы получите самое свежее чтение, которое написано другим потоком.
- Вы не сможете сжечь оптимизацию компилятора JIT.
Ответ 2
Если этот LoadLoad вычисляет значение no-op, то поток 2 может продолжать использовать кешированные значения.
Это покрывается таблицей "Can Order" в кулинарной книге.
Порядок программирования
read b
read a
write a
путем "кеширования a", вы имеете в виду, что код переупорядочен
read a
...
read b
Это переупорядочение запрещено.