Новые оптимизации компилятора VM. Часть 2: Escape-analysis
Мы продолжаем рассказ о новых оптимизациях компилятора JIT виртуальной машины Java HotSpot 1.6. Сегодня речь пойдет о так называемом escape-анализе.
Что представляет собой escape-анализ? Для ответа на этот вопрос сначала необходимо сформулировать исходную проблему.
Представиим себе ситуацию, когда JIT-компилятор начал компиляцию или оптимизацию метода. В общем случае мы предполагаем, что методы, в данный момент выполняющиеся в других потоках, могут изменять любой Java-объект. Однако для объектов, созданных внутри метода, это ограничение может быть смягчено, если удастся доказать, что объект не покидает (escapes) область видимости метода. В соответствии с этим критерием будем называть объекты покидающими и непокидающими.
Пример:
class Escape1 {
Integer val;
Escape1 next;
Escape1(Integer val) { this.val = val }
void example() {
Integer i1 = new Integer(1);
Integer i2 = new Integer(2);
Integer i3 = new Integer(3);
Escape e1 = new Escape1( i1 );
Escape e2 = new Escape1( i2 );
Escape e3 = new Escape1( i3 );
e1.next = e2;
next = e2; // e2 и i2 покидают метод через указатель this
e2.next = e3; // e3 покидает метод через цепочку this => e2
}
}
Объекты i1, e2, e3 являются
покидающими, поскольку они достижимы по ссылкам после выполнения
метода example(). В противоположность этому, объекты i1 и e1 не
являются покидающими.
Формально объект O назыается непокидающим для метода M, если O:
- выделен внутри метода M
- не является объектом класса Thread или производного от него
- не имеет финализатора
- не сохраняется в статическом поле класса или в поле покидающего объекта
- не передается другому методу в качестве аргумента, если только достоверно не известно, что O не покидает и этот метод
- устранение блокировок на непокидающих объектах;
- оптимизация ссылок на объекты. Об этом ниже;
- в некоторых случаях, размещение объекта в стеке или регистрах, а не в куче
Типичные примеры ситуаций, в которых встречаются непокидающие объекты:
- автоматический боксинг (autoboxing) аргуметов метода, если этот метод был встроен в код (inlined) компилятором
- итераторы коллекций
- объекты класса StringBuilder, созданные для конкатенации строк
Теперь обратимся к алгоритму escape-анализа. Для идентификации множества объектов, непокидающих данный метод необходимо проведение вспомогательной процедуры—отслеживания ссылок на объекты. Это—так называемый points-to-анализ. Points-to-анализ—это процесс определения множества объектов, на которые может указывать конкретная ссылка.
В Java HotSpot 1.6 применяется следующий алгоритм escape-анализа:
- для каждой ссылки на объект выполнить points-to-анализ
- объекты, на которые указывают нелокальные ссылки (т.е. определенные вне метода), объявить покидающими
- для каждой локальной ссылки
отметить все объекты, на которые она может указывать, как
покидающие, если:
- эта ссылка сохраняется в поле
покидающего объекта или
- передается в качестве аргумента методу, в котором этот аргумент является покидающим
- эта ссылка сохраняется в поле
покидающего объекта или
Остановимся более внимательно на последнем пункте. Если метод не встроен в код, то необходимо отследить, являются ли его аргументы покидающими данный метод, т. к. в противном случае пришлось бы делать пессимистическое предположение о том, что все аргументы покидают метод. Это перечеркнуло бы эффект оптимизации за счет escape-анализа. Кроме того, нельзя целиком полагаться на статический компилятор, т.к. заранее неизвестно, будет ли JIT-компилятор встраивать данный метод или нет. Тем не менее, в JVM имеется оценочный escape-анализатор, работающий на уровне байткода. Данный анализатор сканирует байткод метода и определяет, какие из аргуметов являются покидающими для метода и является ли покидающим возвращаемое значение. Полученная информация сохраняется для дальнейшего использования динамическим компилятором (JIT).
Теперь рассмотрим пример оптимизации совершаемой с помощью escape-анализа:
class Escape2 {
int fld1, fld2;
Escape2(int v1, int v2) { fld1 = v1; fld2 = v2; }
static void bigMethod() {
// Слишком большой метод, чтобы его встраивать...
}
static void example(int v1, int v2) {
Escape2 e1 = new Escape2(v1, 10);
Escape2 e2 = new Escape2(v2, 5 - v1);
bigMethod();
return e1.fld1 + e2.fld2;
}
}
Без escape-анализа мы должны предполагать, что bigMethod() может изменить поля объектов e1 и e2. Поэтому после его вызова необходимо заново загрузить значения e1.fld1 и e2.fld2. С другой стороны, escape-анализатор скажет нам, что bigMethod() не мог совершить таких изменений, поэтому возвращаемой значение функции example() будет равно v1 + (5 – v1) = 5.
Вообще говоря, можно пойти далее в области escape-анализа и пытаться вычислять множество объектов, не покидающих какой-либо области внутри метода. Это называется потокозависимым анализом. Потокозависимый анализ имеет большую емкостную сложность (т.е. требует больше памяти для выполнения) и может взаимодействовать с другими оптимизациями компилятора. В серверном компиляторе HotSpot реализован потоконезависимый анализ. А кроме того, имеется прототип потокозависимой версии анализатора. В настоящее время исследуется вопрос о том, можно ли за счет большей сложности потокозависимого алгоритма генерировать лучший код.
На данный момент escape-анализ является
прерогативой только серверного компилятора Java SE 6. Escape-анализ отключен по умолчанию, но включить его можно с помощью опции командной строки
-XX:+DoEscapeAnalysis.
Игорь Привалов


опубликовал denis_ka Сентябрь 05, 2006 at 11:31 AM MSD #
Наконец-то его сделали! Я уж думал не дожиму :)
опубликовал Александр Октябрь 27, 2007 at 04:46 PM MSD #
/* Объекты i1, e1, e2 являются покидающими, поскольку они достижимы по ссылкам после выполнения метода example(). В противоположность этому, объекты i1 и e1 не являются покидающими. */
вероятно, объекты e2 и e3 являются покидающими, а не e1 и e2 ?
опубликовал 83.242.250.114 Май 30, 2008 at 09:38 AM MSD #