Обеспечение достойной производительности высокоуровневого языка с динамической типизацией - непростая задача. Just-in-time (JIT) компиляция - динамическая генерация машинного кода с учетом информации, собранной во время выполнения приложения - ключевой элемент производительности виртуальной машины (будь то Java, .NET или даже JavaScript). JIT-компилятор, в свою очередь, должен иметь впечатляющий набор трюков и оптимизаций, что бы компенсировать "динамизм" языка.
В докладе речь пойдет о достижениях современной JIT компиляции в целом и более подробно будут освещены особенности HotSpot JVM (бесплатной JVM от Oracle)
7. Проблемы виртуальных вызовов
Два обращения в память до перехода
• Обращения в память упорядочены
• Конвейер процессор блокирован
Время доступа к памяти
• L1 кэш ~0.5 ns
• L2 кэш ~7 ns
• RAM ~100 ns
8. Проблемы динамических типов
Поля хранятся как хэш-таблица
Доступ к полю
• Арифметическая операция
• Чтение из памяти
• Условная операция
• Чтение из памяти
9. Как сделать быстрый интерпретатор?
switch(byteCode) {
case STORE: ...
case LOAD: ...
case ASTORE: ...
case ALOADE: ...
...
}
?
10. Как сделать быстрый интерпретатор?
Интерпретатор HotSpot JVM
• Для каждой инструкции написана процедура на
ассемблере
• Dispath – jump по адресу из таблицы процедур
• Каждая процедура заканчивается jump на dispatch
Интерпретация кода в одном кадре стека
Код и таблица переходов кэшируются
Конвейер процессора остаётся загруженным
11. Подходы к JIT компиляции
Классический подход
Независимая компиляция методов
+ использование динамических оптимизаций
Трассирующая компиляция
Генерация кода для участков без ветвления
+ использование гардов
12. Подходы к JIT компиляции
Классический подход
Независимая компиляция методов
• JVM, V8, Ion Monkey
Трассирующая компиляция
Генерация кода для участков без ветвления
• Flash, Trace Monkey, PyPy, LuaJIT
13. Трассирующий JIT
Интерпретация
• Логирование операций и условий ветвления
Профилирование
• Выявление часто используемых трасс
Компиляция трасс
•
•
•
•
Машинный код без ветвлений
Гарды в местах проверки условий
Глобальная оптимизация трассы
Нарушение гарда – возврат к интерпретации
14. Трассирующий JIT
Достоинства
• Девиртуализация и инлайнинг
• Сглаживает проблему динамических типов
• Глубокая оптимизация горячих участков
Недостатки
• Трассировка – ОЧЕНЬ медленная интерпретация
• Долгое время разогрева
15. Проблема виртуальных типов
V8 – скрытые типы
• Строгая типизация во время выполнения
TraceMonkey – shape inference/property cache
• “Инлайн” кэш в скомпилированном коде
LuaJIT – трейс компиляция поиска по хешу
HREFK: if (hash[17].key != key) goto exit
HLOAD: x = hash[17].value
-orHSTORE: hash[17].value = x
18. HotSpot JVM JIT
• Быстрый интерпретатор
• Два JIT компилятора (C1 / C2)
• Профилирование для управления компиляцией
• Деоптимизация кода
• On Stack Replacement (OSR)
19. Девиртуализация
Профилирование точек вызовов (call site)
• Мономорфный – большинство переходов на
одну реализацию
• Биморфный - большинство переходов на одну из
двух реализаций
• Полиморфный
21. Инкрементальная компиляция
Collections.indexedBinarySearch()
Полиморфный
…
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
…
MyPojo
Полиморфный
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);
}
22. Инкрементальная компиляция
JIT компилирует MyPojo.get()
• Collections.binarySort() – инлайнится
Вызовы в Collections.binarySort() становятся
мономорфными
JIT продолжает профилирование и
перекомпилирует метод
Вызовы get() и compareTo()
девиртуализированны и заинлайнены
23. On Stack Replacement
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;
}
JIT может перекомпилировать main и подменить
точку возврата на стеке, находясь внутри цикла
24. Escape analysis
Тяжёлое наследие молодости – synchronize
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("X=").append(x);
buf.append(",Y=").append(y);
return buf.toString();
}
buf не выходит за пределы метода
все методы buf заинлайнены
удаляем код синхронизации
25. Scalar replacement
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);
}
После инлайна distance в length
JIT заменяет объекты Point на скалярные переменные
26. Сборка мусора и JIT
public class Singleton {
public static final
Singleton INSTANCE = new Singleton()
}
JIT инлайнит final static переменные
• Адрес объекта в памяти в машинном коде
• С точки зрения GC код метода структура
Учитывается как корень при маркировке
Адрес в коде корректируется при перемещении объекта