View
3.188
Download
0
Category
Preview:
DESCRIPTION
Обеспечение достойной производительности высокоуровневого языка с динамической типизацией - непростая задача. Just-in-time (JIT) компиляция - динамическая генерация машинного кода с учетом информации, собранной во время выполнения приложения - ключевой элемент производительности виртуальной машины (будь то Java, .NET или даже JavaScript). JIT-компилятор, в свою очередь, должен иметь впечатляющий набор трюков и оптимизаций, что бы компенсировать "динамизм" языка. В докладе речь пойдет о достижениях современной JIT компиляции в целом и более подробно будут освещены особенности HotSpot JVM (бесплатной JVM от Oracle)
Citation preview
JIT-компиляция в виртуальной машине Java
Алексей Рагозин
Обзор доклада
Проблемы динамических языков
Виртуальные вызовы
Нетипизированные данные
Подходы к JIT компиляции
Method based JIT
Tracing JIT
“Грязные трюки” JIT компиляторов
Старый добрый C++
Простое наследование
010110010010
101010100110
101010100101
010101001010
101010101010
101010101010
101010101010
101010100010
00: methodA
02: methodC
03: methodD
CODEOBJECT
VTABLE
01: methodB
Старый добрый C++
Множественное наследование
010110010010
101010100110
101010100101
010101001010
101010101010
101010101010
101010101010
101010100010
111010100100
011110000010
101001010100
00: methodA
02: methodC
03: methodD
CODEOBJECT
VTABLE
01: methodB
00: methodX
02: methodZ
01: methodY
VTABLE
Старый добрый C++
Множественное наследование
A
B C
D D
A
B C
D
Проблемы виртуальных вызовов
Branch misprediction penalty
• Intel Nehalem – 17 cycles
• Intel Sandy/Ivy bridge – 15 cycles
• Intel Haskwell – 15 - 20 cycles
• AMD K8 / K10 – 13 cycles
• AMD Buldozer – 19 - 22 cycles
http://www.agner.org/optimize/microarchitecture.pdf
Проблемы виртуальных вызовов
Два обращения в память до перехода
• Обращения в память упорядочены
• Конвейер процессор блокирован
Время доступа к памяти
• L1 кэш ~0.5 ns
• L2 кэш ~7 ns
• RAM ~100 ns
Проблемы динамических типов
Поля хранятся как хэш-таблица
Доступ к полю
• Арифметическая операция
• Чтение из памяти
• Условная операция
• Чтение из памяти
Как сделать быстрый интерпретатор?
switch(byteCode) {
case STORE: ...
case LOAD: ...
case ASTORE: ...
case ALOADE: ...
...
}
?
Как сделать быстрый интерпретатор?
Интерпретатор HotSpot JVM
• Для каждой инструкции написана процедура на ассемблере
• Dispath – jump по адресу из таблицы процедур
• Каждая процедура заканчивается jump на dispatch
Интерпретация кода в одном кадре стека
Код и таблица переходов кэшируются
Конвейер процессора остаётся загруженным
Подходы к JIT компиляции
Классический подход Независимая компиляция методов
+ использование динамических оптимизаций
Трассирующая компиляция Генерация кода для участков без ветвления
+ использование гардов
Подходы к JIT компиляции
Классический подход Независимая компиляция методов
• JVM, V8, Ion Monkey
Трассирующая компиляция Генерация кода для участков без ветвления
• Flash, Trace Monkey, PyPy, LuaJIT
Трассирующий JIT
Интерпретация • Логирование операций и условий ветвления
Профилирование • Выявление часто используемых трасс
Компиляция трасс • Машинный код без ветвлений
• Гарды в местах проверки условий
• Глобальная оптимизация трассы
• Нарушение гарда – возврат к интерпретации
Трассирующий JIT
Достоинства
• Девиртуализация и инлайнинг
• Сглаживает проблему динамических типов
• Глубокая оптимизация горячих участков
Недостатки
• Трассировка – ОЧЕНЬ медленная интерпретация
• Долгое время разогрева
Проблема виртуальных типов
V8 – скрытые типы
• Строгая типизация во время выполнения
TraceMonkey – shape inference/property cache
• “Инлайн” кэш в скомпилированном коде
LuaJIT – трейс компиляция поиска по хешу HREFK: if (hash[17].key != key) goto exit
HLOAD: x = hash[17].value
-or-
HSTORE: hash[17].value = x
Ссылки
1. LuaJIT http://article.gmane.org/gmane.comp.lang.lua.general/58908
2. Incremental Dynamic Code Generation with Trace Trees http://www.ics.uci.edu/~franz/Site/pubs-pdf/ICS-TR-06-16.pdf
3. V8 Design aspects https://developers.google.com/v8/design
4. RPython http://tratt.net/laurie/research/pubs/papers/bolz_tratt__the_impact_of_metatracing_on_vm_design_and_implementation.pdf
HotSpot JVM
HotSpot JVM JIT
• Быстрый интерпретатор
• Два JIT компилятора (C1 / C2)
• Профилирование для управления компиляцией
• Деоптимизация кода
• On Stack Replacement (OSR)
Девиртуализация
Профилирование точек вызовов (call site)
• Мономорфный – большинство переходов на одну реализацию
• Биморфный - большинство переходов на одну из двух реализаций
• Полиморфный
Девиртуализация
“Инлайн” кэш переходов
if (list.getClass == ArrayList.class) {
/* NON VIRTUAL */ list.ArrayList#size()
}
else {
/* VIRTUAL */ list.size();
}
Инкрементальная компиляция
Collections.indexedBinarySearch()
MyPojo
…
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
…
Полиморфный
Полиморфный
List<String> keys = new ArrayList<String>();
List<String> vals = new ArrayList<String>();
public String get(String key) {
int n = Collections.binarySearch(keys, key);
return n < 0 ? null ? vals.get(n);
}
Инкрементальная компиляция
JIT компилирует MyPojo.get() • Collections.binarySort() – инлайнится
Вызовы в Collections.binarySort() становятся мономорфными
JIT продолжает профилирование и перекомпилирует метод
Вызовы get() и compareTo() девиртуализированны и заинлайнены
On Stack Replacement
JIT может перекомпилировать main и подменить точку возврата на стеке, находясь внутри цикла
public static void main() {
long s = System.nanotime();
for(int i = 0; i != N; ++i) {
/* a lot of code */
...
}
long avg = (System.nanotime() - s) / N;
}
Escape analysis
Тяжёлое наследие молодости – synchronize
buf не выходит за пределы метода
все методы buf заинлайнены
удаляем код синхронизации
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("X=").append(x);
buf.append(",Y=").append(y);
return buf.toString();
}
Scalar replacement
После инлайна distance в length
JIT заменяет объекты Point на скалярные переменные
public double length() {
return distance(
new Point(ax, ay),
new Point(bx, by));
}
public double distance(Point a, Point b) {
double w = a.x - b.x;
double h = a.y - b.y;
return Math.sqrt(w*w + h*h);
}
Сборка мусора и JIT
JIT инлайнит final static переменные
• Адрес объекта в памяти в машинном коде
• С точки зрения GC код метода структура Учитывается как корень при маркировке
Адрес в коде корректируется при перемещении объекта
public class Singleton {
public static final
Singleton INSTANCE = new Singleton()
}
Оптимизация кода
“Красивые самолёты летают быстрее” – поговорка авиаконструкторов
Спасибо Алексей Рагозин (alexey.ragozin@gmail.com)
http://blog.ragozin.info
http://aragozin.timepad.ru
Recommended