Модель памяти Java: компилятор переупорядочивает строки кода

Хорошо известно, что язык Java позволяет компиляторам переупорядочивать строки скомпилированного кода до тех пор, пока переупорядочивание не влияет на семантику кода. Однако компилятор должен заботиться только о сематике, как видно из текущего потока. Если это изменение порядка влияет на семантику в многопоточной ситуации, это обычно вызывает проблемы параллелизма (видимости памяти).

Мои вопросы) :

  1. Что достигается предоставлением этой свободы компилятору? Действительно ли компилятор может создать более эффективный код, перестроив код? Я еще не видел практического случая для этого. Иногда я чувствую, что преимущества, если таковые имеются, намного перевешиваются рисками параллелизма, которые это может привести.

  2. Есть ли способ, которым программист может сказать компилятору не переставлять строки таким образом? Я знаю, что использование примитивов синхронизации эффективно справляется с побочными эффектами перестановки, но я спрашиваю, есть ли какой-либо прямой способ (опция компилятора) отключить это?


person Bhaskar    schedule 05.01.2012    source источник


Ответы (3)


Компилятор javac почти не выполняет оптимизацию.

Собственный JIT-компилятор может изменить порядок инструкций там, где есть проблема с порядком памяти. Однако ЦП также может переупорядочивать инструкции и обновления памяти, которые имеют тот же эффект.

Что достигается предоставлением этой свободы компилятору?

Главное преимущество — переносимость кода. Чем больше гарантий вы предоставляете, тем сложнее убедиться, что каждая платформа действительно делает это.

Существует также значительное улучшение производительности, поскольку ЦП позволяет выполнять инструкции по мере возможности, а не в строгом порядке.

Действительно ли компилятор может создать более эффективный код, перестроив код?

Да. но переупорядочивание, выполняемое ЦП, более важно.

Я еще не видел практического случая для этого. Иногда я чувствую, что преимущества, если таковые имеются, намного перевешиваются рисками параллелизма, которые это может привести.

Есть ли способ, которым программист может сказать компилятору не переставлять строки таким образом?

Вот почему вы используете барьеры памяти, такие как блоки volatile, synchronized и Lock. Когда вы используете их, вы получаете гарантии безопасности потоков.

Я знаю, что использование примитивов синхронизации эффективно справляется с побочными эффектами перестановки, но я спрашиваю, есть ли какой-либо прямой способ (опция компилятора) отключить это?

Вы можете отключить JIT, но большая часть переупорядочения выполняется процессором, поэтому он не принесет многого.

Избегание переупорядочивания обновлений — это такая небольшая часть проблемы безопасности потоков (самая большая проблема заключается в том, что она неясна и редко возникает, что затрудняет ее тестирование). И как только вы пишете код, безопасный для потоков, это облегчается.

person Peter Lawrey    schedule 05.01.2012

Процесс реорганизации байт-кода управляется JIT (компилятор Just in Time). Вы можете остановить его запуск с помощью следующей опции:

-Djava.compiler=NONE

Подробнее см. здесь: http://www.cs.swarthmore.edu/~newhall/unixhelp/debuggingtips_Java.html

person Dan Hardiker    schedule 05.01.2012
comment
Это не остановит инструкции по переупорядочению ЦП. ;) - person Peter Lawrey; 05.01.2012
comment
И это заставляет вашу программу работать очень медленно, так что это не тот вариант, который полезен для любого производственного использования. - person Jesper; 05.01.2012

Действительно ли компилятор может создать более эффективный код, перестроив код?

О да, это так!

Одним из фактов современной компьютерной архитектуры является то, что память является основным узким местом производительности. ЦП может выполнять инструкции регистра для регистрации во много раз быстрее, чем он может читать и записывать в основную память. Вот почему высокопроизводительные чипы имеют 2 или 3 уровня кэш-памяти. Кроме того, типичные процессоры используют конвейерную обработку, что позволяет одновременно выполнять несколько инструкций.

Чтобы получить максимальную производительность от ЦП с этими свойствами, компилятор (и сам ЦП) должен иметь возможность переупорядочивать инструкции, чтобы наилучшим образом использовать пропускную способность памяти и поддерживать заполнение конвейера. Собственный код также должен иметь возможность использовать значения (переменных), сохраненные в регистрах, и свести к минимуму использование инструкций барьера памяти, которые ожидают записи в память для распространения в основную память. Они разрешены (в Java) только потому, что модель памяти Java допускает переупорядочивание.

Примечание: здесь речь идет о значительном ускорении: может быть, от 3 до 5 раз.

Если вы хотите получить представление о том, насколько много, возьмите приложение с интенсивным использованием переменных экземпляра и перепишите его так, чтобы все переменные экземпляра были volatile. (Это уменьшит область переупорядочения и приведет к тому, что все операции чтения и записи будут выполняться в памяти.) Затем сравните производительность исходного приложения с модифицированной версией.

person Stephen C    schedule 05.01.2012