Прочитав «Поваренную книгу JSR-133 для разработчиков компиляторов» о реализации volatile, особенно в разделе «Взаимодействие с атомарными командами», я предполагаю, что для чтения изменчивой переменной без ее обновления требуется барьер LoadLoad или LoadStore. Далее по странице я вижу, что LoadLoad и LoadStore фактически не работают на процессорах X86. Означает ли это, что операции чтения volatile могут выполняться без явного аннулирования кеша на x86 и так же быстро, как обычное чтение переменной (без учета ограничений переупорядочения volatile)?
Думаю, я неправильно это понимаю. Может ли кто-нибудь просветить меня?
РЕДАКТИРОВАТЬ: Интересно, есть ли различия в многопроцессорных средах. В однопроцессорных системах ЦП может смотреть на свои собственные кэши потоков, как заявляет Джон В., но в многопроцессорных системах должен быть какой-то параметр конфигурации для ЦП, что этого недостаточно, и необходимо задействовать основную память, что замедляет энергозависимость в системах с несколькими процессорами, верно?
PS: По пути, чтобы узнать больше об этом, я наткнулся на следующие замечательные статьи, и, поскольку этот вопрос может быть интересен другим, я поделюсь своими ссылками здесь:
источник
Ответы:
На Intel неконтролируемое чтение изменчивой информации довольно дешево. Если мы рассмотрим следующий простой случай:
Используя возможность Java 7 печатать код сборки, метод run выглядит примерно так:
Если вы посмотрите на 2 ссылки на getstatic, первая включает в себя загрузку из памяти, вторая пропускает загрузку, поскольку значение повторно используется из регистров, в которые оно уже загружено (long - 64 бит и на моем 32-битном ноутбуке он использует 2 регистра).
Если мы сделаем переменную l изменчивой, то получится другая сборка.
В этом случае обе ссылки getstatic на переменную l включают загрузку из памяти, то есть значение не может сохраняться в регистре при многократном чтении из энергозависимой памяти. Чтобы обеспечить атомарное чтение, значение считывается из основной памяти в регистр MMX,
movsd 0x6fb7b2f0(%ebp),%xmm0
что делает операцию чтения одной инструкцией (из предыдущего примера мы видели, что для 64-битного значения обычно требуется два 32-битных чтения в 32-битной системе).Таким образом, общая стоимость энергозависимого чтения будет примерно эквивалентна загрузке памяти и может быть такой же дешевой, как доступ к кеш-памяти L1. Однако, если другое ядро выполняет запись в изменчивую переменную, строка кэша станет недействительной, что потребует доступа к основной памяти или, возможно, к кэш-памяти L3. Фактическая стоимость будет сильно зависеть от архитектуры процессора. Даже между Intel и AMD протоколы согласованности кеша различаются.
источник
Вообще говоря, на большинстве современных процессоров нестабильная нагрузка сопоставима с нормальной нагрузкой. Энергозависимое хранилище составляет примерно 1/3 времени montior-enter / monitor-exit. Это наблюдается в системах с когерентным кешем.
Чтобы ответить на вопрос OP, изменчивые записи дороги, в то время как чтения обычно нет.
Да, иногда при проверке поля ЦП может даже не воздействовать на основную память, вместо этого шпионит за кешами других потоков и получает оттуда значение (очень общее объяснение).
Тем не менее, я повторяю предложение Нила о том, что если у вас есть поле, доступное для нескольких потоков, вы должны обернуть его как AtomicReference. Будучи AtomicReference, он выполняет примерно такую же пропускную способность для чтения / записи, но также более очевидно, что к полю будут обращаться и изменять его несколько потоков.
Изменить, чтобы ответить на редактирование OP:
Согласованность кэша - это немного сложный протокол, но вкратце: ЦП будут использовать общую строку кэша, которая прикреплена к основной памяти. Если ЦП загружает память, а у других ЦП ее нет, ЦП будет считать, что это самое последнее значение. Если другой ЦП пытается загрузить то же место в памяти, уже загруженный ЦП будет знать об этом и фактически будет передавать кэшированную ссылку на запрашивающий ЦП - теперь ЦП запроса имеет копию этой памяти в своем кэше ЦП. (Ему никогда не приходилось искать ссылку в основной памяти)
Здесь задействовано немного больше протокола, но это дает представление о том, что происходит. Также, чтобы ответить на ваш другой вопрос, при отсутствии нескольких процессоров энергозависимое чтение / запись на самом деле может быть быстрее, чем с несколькими процессорами. Есть некоторые приложения, которые на самом деле будут работать быстрее одновременно с одним процессором, чем с несколькими.
источник
Говоря словами модели памяти Java (как определено для Java 5+ в JSR 133), любая операция - чтение или запись - с
volatile
переменной создает отношение « происходит до» по отношению к любой другой операции с той же переменной. Это означает, что компилятор и JIT вынуждены избегать определенных оптимизаций, таких как переупорядочивание инструкций внутри потока или выполнение операций только в локальном кэше.Поскольку некоторые оптимизации недоступны, результирующий код обязательно медленнее, чем был бы, хотя, вероятно, не намного.
Тем не менее, вам не следует создавать переменную,
volatile
если вы не знаете, что доступ к ней будет осуществляться из нескольких потоков внеsynchronized
блоков. Даже тогда вы должны рассмотреть вопрос о том летучий является лучшим выбором по сравнению сsynchronized
,AtomicReference
и его друзья, явныеLock
классы и т.д.источник
Доступ к изменчивой переменной во многом похож на обертывание доступа к обычной переменной в синхронизированном блоке. Например, доступ к изменчивой переменной не позволяет ЦП переупорядочивать инструкции до и после доступа, и это обычно замедляет выполнение (хотя я не могу сказать, насколько).
В более общем плане, в многопроцессорной системе я не вижу, как доступ к изменчивой переменной может быть выполнен без штрафных санкций - должен быть какой-то способ гарантировать, что запись на процессоре A будет синхронизирована с чтением на процессоре B.
источник