Почему разделяемые переменные кэшируются в кэш-памяти процессора?
Я пытаюсь понять модель памяти Java, но не смог понять, что такое кэши ЦП.
Насколько я знаю, в JVM у нас есть следующие места для хранения локальных и общих переменных:
local variables -- on thread stack
shared variables -- in memory, but every CPU cache has a copy of it
Итак, мой вопрос: зачем хранить локальные переменные в стеке, а (кешировать) общие переменные в кеше процессора? Почему бы не наоборот (предположим, что кэш-память процессора слишком дорога для хранения обоих), мы кэшируем локальные переменные в кэш-памяти процессора и просто выбираем общие переменные из памяти? Это часть дизайна языка Java или компьютерной архитектуры?
Далее: так просто, как звучит "кэш процессора", что если несколько процессоров используют один кэш? А в системах с многоуровневым кэшем, на каком уровне кэша будет храниться копия общих переменных? Кроме того, если в одном ядре ЦП запущено более 1 потока, означает ли это, что они совместно используют один и тот же набор кэшированных общих переменных, и, следовательно, даже если общая переменная не определена volatile
, доступ к переменной все еще мгновенно виден другим потокам, работающим на том же процессоре?
1 ответ
"Локальные" и "общие" переменные не имеют смысла вне контекста вашего кода. Они не влияют на то, где или даже если состояние кэшируется. Это даже не полезно думать или рассуждать с точки зрения того, где хранится ваше состояние; единственная причина, по которой существует JMM, заключается в том, что такие детали, которые варьируются от архитектуры к архитектуре, не предоставляются программисту. Опираясь на сведения об оборудовании низкого уровня, вы задаете неправильные вопросы о JMM. Это не полезно для вашего приложения, оно делает его хрупким, легче ломать, труднее рассуждать и менее переносимым.
Тем не менее, как правило, вы должны предполагать, что любое состояние программы, если не все состояния, может быть кэшировано. Дело в том, что то, что кешируется, на самом деле не имеет значения, просто все и вся может быть, будь то примитивные типы или ссылочные типы, или даже переменные состояния, инкапсулированные несколькими полями. Какие бы инструкции ни выполнялся потоком (и эти инструкции также различаются в зависимости от архитектуры - будьте осторожны!), Эти инструкции по умолчанию возвращаются на ЦПУ, чтобы определить, что относится к кешу, а что нет к кешу; программисты не могут сделать это сами (хотя можно повлиять на то, где могут кэшироваться переменные состояния, посмотрите, что такое ложное совместное использование).
Опять же, мы также можем сделать еще несколько обобщений о x86, что активные примитивные типы, вероятно, помещаются в регистры, потому что P/ALU смогут работать с ними быстрее всего. Все остальное идет, хотя. Примитивы могут быть перемещены в кэш-память L1/2, если они являются локальными для ядра, и, безусловно, возможно, что они будут перезаписаны довольно быстро. Процессор может поместить переменные состояния в общий L3, если он думает, что в будущем произойдет переключение контекста, или не смог. Специалист по аппаратному обеспечению должен будет ответить на это.
В идеале переменные состояния должны храниться в ближайшем кеше (регистр, L1/2/3, затем основная память) к процессору. Это зависит от процессора, чтобы решить, хотя. Невозможно рассуждать о семантике кэша на уровне Java. Даже если гиперпоточность включена (я не уверен, что такое эквивалент AMD), потокам не разрешается совместно использовать ресурсы, и даже тогда, если они были, напомним, что видимость не единственная проблема, связанная с переменными общего состояния; в случае, когда процессор выполняет конвейерную обработку, вам все еще нужны соответствующие инструкции для обеспечения правильного упорядочения (даже после того, как вы избавитесь от буферизации чтения / записи на ЦП), будь то hwsync
или соответствующие заборы или другие.
Опять же, рассуждать о свойствах кэша бесполезно, потому что JMM обрабатывает это для вас и потому, что он неопределим, где / когда / что кэшируется. Кроме того, даже если вы знаете, где, когда и какие вопросы, вы все равно не можете рассуждать о видимости данных; в любом случае все кэши обрабатывают кэшированные данные одинаково, и вам нужно полагаться на то, что процессор обновляет состояние кэша между состояниями SI (O) ME, упорядочением команд, буферизацией загрузки / сохранения, обратной / сквозной записью и т. д. И вы еще не сталкивались с проблемами, которые могут возникнуть на уровне ОС и JVM. Опять же, к счастью, JDK позволяет вам использовать основные инструменты, такие как volatile
, final
и атомарность, которая работает согласованно на всех платформах и производит код, который является предсказуемым и легко (э) обоснованным.