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

Я знаю, что модель памяти JVM предназначена для наименьшего общего знаменателя процессоров, поэтому она должна предполагать самую слабую возможную модель процессора, на котором может работать JVM (например, ARM).

Теперь, учитывая, что x64 имеет довольно сильную модель памяти, какие методы синхронизации я могу игнорировать, предполагая, что моя программа будет работать только на 64-битных процессорах x86? Также это применимо, когда моя программа запускается через виртуализацию?

Пример:
Известно, что модель памяти JVM требует синхронизации доступа для чтения/записи к long и double, но можно предположить, что чтение/запись других 32-битных примитивов, таких как int, float и т. д., являются атомарными.

Однако, если я знаю, что работаю на 64-битной машине x86, могу ли я игнорировать использование блокировок на длинных/двойных значениях, зная, что процессор будет атомарно читать/записывать 64-битные значения и просто сохранять их изменчивыми (как я сделал бы с целыми/поплавками )?


person pdeva    schedule 23.07.2014    source источник


Ответы (5)


Я знаю, что модель памяти JVM предназначена для наименьшего общего знаменателя процессоров, поэтому она должна предполагать самую слабую возможную модель процессора, на котором может работать JVM (например, ARM).

Это неправильно. JMM возникла в результате компромисса между множеством конкурирующих сил: стремлением к более слабой модели памяти, чтобы программы могли работать быстрее на оборудовании со слабыми моделями памяти; желание авторов компиляторов разрешить определенные оптимизации; и стремление к тому, чтобы результат параллельных Java-программ был корректным и предсказуемым, а по возможности (!) понятным Java-программистам. См. CACM Сариты Адве. статью с общим обзором проблем с моделями памяти.

Учитывая, что x64 имеет довольно сильную модель памяти, какие методы синхронизации я могу игнорировать, предполагая, что моя программа будет работать только на процессорах [x64]?

Никто. Проблема в том, что модель памяти применяется не только к базовому оборудованию, но и к JVM, выполняющей вашу программу, и в основном к JIT-компилятору JVM. Компилятор может принять решение о применении определенных оптимизаций, разрешенных в рамках модели памяти, но если ваша программа делает необоснованные предположения о поведении памяти на основе базового оборудования, ваша программа не сработает.

Вы спрашивали о x64 и атомарной 64-битной записи. Возможно, на x64-машине разрыва слов никогда не произойдет. Сомневаюсь, что какой-либо JIT-компилятор в целях оптимизации разорвал бы 64-битное значение на 32-битные записи, но кто знает. Однако кажется маловероятным, что вы могли бы использовать эту функцию, чтобы избежать синхронизации или изменчивых полей в вашей программе. Без них записи в эти переменные могут никогда не стать видимыми для других потоков, или они могут быть произвольно переупорядочены по отношению к другим записям, что может привести к ошибкам в вашей программе.

Мой совет - сначала правильно применить синхронизацию, чтобы ваша программа была правильной. Вы можете быть приятно удивлены. Операции синхронизации были сильно оптимизированы и в общем случае могут быть очень быстрыми. Если вы обнаружите узкие места, рассмотрите возможность использования таких оптимизаций, как разделение блокировки, использование volatile или преобразование в неблокирующие алгоритмы.

ОБНОВЛЕНИЕ

ОП обновил вопрос, чтобы уточнить использование volatile вместо блокировок и синхронизации.

Оказывается, volatile имеет не только семантику видимости памяти. Это также делает long и double доступ атомарным, чего нельзя сказать о не-5_ переменных этих типов. См. раздел JLS 17.7. Вы должны иметь возможность полагаться на volatile для обеспечения атомарности на любом оборудовании, а не только на x64.

Пока я этим занимаюсь, дополнительную информацию о модели памяти Java см. в стенограмме выступления JMM Pragmatics Алексея Шипилева. . (Алексей также является парнем JMH.) В этом докладе много подробностей и несколько интересных упражнений. чтобы проверить свое понимание. Один общий вывод из разговора заключается в том, что часто бывает ошибкой полагаться на свою интуицию в отношении того, как работает модель памяти, например. с точки зрения строк кэша или буферов записи. JMM — это формализм операций с памятью и различных ограничений (синхронизируется с, происходит до и т. д.), определяющих порядок этих операций. . Это может привести к совершенно нелогичным результатам. Неразумно пытаться перехитрить JMM, думая о конкретных аппаратных свойствах. Он вернется, чтобы укусить вас.

person Stuart Marks    schedule 26.07.2014
comment
Отличный ответ! Возможно, стоит отметить, что большинство этих методов синхронизации почти бесплатны... если только они не необходимы. - person G. Blake Meike; 28.07.2014
comment
@ G.BlakeMeike Спасибо! Да, синхронизация была значительно оптимизирована. Я добавил примечание по этому поводу. Но я никогда не говорю «бесплатно» или «почти бесплатно»… тогда кто-нибудь скажет: «Хорошо, а можно мне их миллиард?» :-) - person Stuart Marks; 28.07.2014
comment
Компилятор JIT не будет намеренно разрывать 64-битное значение на 32-битные записи на x64, но на самом деле простой 64-битный доступ является атомарным, только если место хранения правильно выровнено. Таким образом, для полей, отличных от volatile, JVM может использовать более расслабленное выравнивание, что приводит к разрыву слов даже на x64. Это будет не оптимизация, а скорее ее отсутствие. Или, ну, это может быть частью стратегии экономии памяти… - person Holger; 29.07.2014
comment
На самом деле, я не уверен, с какой проблемой сталкивается ОП. Если вас беспокоит разрыв 64-битной записи в основную память, мы рассматриваем сценарий, в котором одно ядро ​​записывает это значение, а другое читает. Если эти ядра используют один и тот же кэш L1, а Хольгер прав, невыровненный доступ может завершиться ошибкой. Если они находятся в разных кешах, другое ядро ​​будет наблюдать только результат, поскольку он записывается обратно в основную память (или на самый высокий общий уровень кеша). Не знаю, как здесь работают операции обратной записи. - person Ralf H; 30.07.2014
comment
@Holger Чистое предположение здесь: компилятор JIT может отделить некоторые итерации от цикла for по длинным и записать только младшие 32 бита, поскольку он может предположить, что старшие 32 бита не меняются. Если бы другой поток записал полное 64-битное значение, результатом могло бы быть 64-битное значение, которое фактически никогда не записывалось ни одним потоком. Я понятия не имею, имеет ли это какую-либо практическую ценность, но кажется, что для JIT законно делать это для энергонезависимого долгого времени. - person Stuart Marks; 30.07.2014
comment
@RalfH Да, я тоже не уверен, что нужно OP. OP, похоже, хочет делать предположения для программ Java на основе оборудования, на котором они работают, но мне трудно понять, какие преимущества можно получить от этого. Недостатком является то, что JIT может нарушить предположения, которые пытается сделать OP, просто потому, что это не происходит на оборудовании. - person Stuart Marks; 30.07.2014
comment
@Stuart Marks: это может иметь практическое применение, если оптимизатор способен использовать SSE. Тогда расчет в 32-битном, а не в 64-битном режиме означает удвоение количества возможных параллельных вычислений. - person Holger; 30.07.2014
comment
@StuartMarks Преимущество, если оно возможно, заключается в том, что можно написать код, который будет чище/имеет меньше строк. конечно весь смысл поста в том чтобы понять в чем, если вообще возможно такое преимущество - person pdeva; 31.07.2014

вам все равно нужно будет обрабатывать потокобезопасность, поэтому семантика волатильности и ограничения памяти по-прежнему будут иметь значение

Я имею в виду, что, например, в Oracle Java большинство низкоуровневых операций синхронизации заканчиваются в Unsafe (docjar.com/docs/api/sun/misc/Unsafe.html#getUnsafe), который, в свою очередь, имеет длинный список нативных операций. методы. Так что, в конце концов, эти методы синхронизации и множество других низкоуровневых операций инкапсулируются JVM там, где им и место. x64 не имеет такой же jvm, как x86.

после повторного чтения вашего отредактированного вопроса: атомарность ваших операций загрузки/сохранения была темой здесь. Так что нет, вам не нужно беспокоиться об атомарной 64-битной загрузке/сохранении на x64. Но поскольку это не конец всех проблем с синхронизацией, см. другие ответы.

person Ralf H    schedule 23.07.2014

Всегда включайте барьеры памяти там, где модель памяти JVM указывает, что они необходимы, а затем позволяйте JVM оптимизировать их, когда это возможно для разных платформ.

Знание того, что вы работаете только на процессорах x86, не означает, что вы можете отказаться от использования барьеров памяти. Если, возможно, вы не знаете, что будете работать только на одноядерном процессоре x86 ;) Что в современном многоядерном мире никто не знает.

Почему? Потому что модель памяти Java имеет две основные проблемы.

  1. видимость данных между ядрами и
  2. бывает до гарантий, то есть повторный заказ.

Без барьера памяти в игре порядок операций, которые становятся видимыми для других ядер, может стать очень запутанным; и это даже с более сильными гарантиями, предлагаемыми x86. x86 обеспечивает согласованность только после того, как данные попадают в кеш ЦП, и, хотя его гарантии упорядочения очень надежны, они срабатывают только после того, как Hotspot сообщит ЦП о необходимости записи в кеш.

Без volatile/synchronized компиляторы (javac и hotspot) будут решать, когда они будут выполнять эти записи и в каком порядке. Для них совершенно справедливо решение хранить данные в регистрах в течение длительного времени. Как только барьер энергозависимой или синхронизированной памяти пересекается, JVM знает, что нужно сообщить ЦП, чтобы он отправил данные в кеш.

Как документирует Дуг Ли в JSR-133 Cookbook, большинство барьеров x86 сводятся к инструкциям без операций, которые гарантируют порядок. Таким образом, JVM сделает инструкции максимально эффективными для нас. Кодируйте модель памяти Java, и пусть Hotspot творит чудеса. Если Hotspot сможет доказать, что синхронизация не требуется, она может полностью отказаться от нее.

Наконец, было доказано, что шаблон блокировки с двойной проверкой не работает и на многоядерных процессорах x86; несмотря на его более сильные гарантии памяти. Некоторые интересные детали этого были написаны Бартосом Милевски в его C++ блог и снова, на этот раз конкретно для Java здесь

person Chris K    schedule 01.08.2014

Компилятор пишет, позаботился о том, что вы хотели сделать. Многие из энергозависимых барьеров чтения/записи в конечном итоге перестанут работать на x64. Также думайте, что переупорядочение также может быть вызвано оптимизацией компилятора и может не зависеть от оборудования. Например, безобидные гонки данных - например, String hashCode. См.: http://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html

Также, пожалуйста, обратитесь к странице, чтобы узнать, какие инструкции могут быть недоступны на x64. См.: http://gee.cs.oswego.edu/dl/jmm/cookbook.html см. раздел «Мультипроцессоры».

Я бы посоветовал не делать никаких оптимизаций, специфичных для аппаратного обеспечения. В конечном итоге вы можете написать неподдерживаемый код. Разработчики компиляторов уже выполнили достаточно тяжелой работы.

person veritas    schedule 23.07.2014
comment
на самом деле переупорядочивание выполняется в процессоре после декодирования инструкций из памяти и создания микроопераций. Однако это может поддерживаться правильной оптимизацией компилятора. Они должны учитывать количество доступных регистров, определенные тайминги инструкций и задержки, а также множество другой информации, относящейся к процессору. - person Ralf H; 25.07.2014
comment
См. 3-й пункт Оптимизация потока управления publib.boulder.ibm.com/infocenter/javasdk/v1r4m2/ - person veritas; 25.07.2014

Это зависит не только от процессора, но и от JVM, операционной системы и т. д.

В одном вы можете быть уверены: ничего не предполагайте, если дело касается синхронизации потоков.

person maslan    schedule 30.07.2014