JIT-компиляция в виртуальной машине Java (HighLoad++ 2013)

5,204 views

Published on

Обеспечение достойной производительности высокоуровневого языка с динамической типизацией - непростая задача. Just-in-time (JIT) компиляция - динамическая генерация машинного кода с учетом информации, собранной во время выполнения приложения - ключевой элемент производительности виртуальной машины (будь то Java, .NET или даже JavaScript). JIT-компилятор, в свою очередь, должен иметь впечатляющий набор трюков и оптимизаций, что бы компенсировать "динамизм" языка.

В докладе речь пойдет о достижениях современной JIT компиляции в целом и более подробно будут освещены особенности HotSpot JVM (бесплатной JVM от Oracle)

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
5,204
On SlideShare
0
From Embeds
0
Number of Embeds
2,837
Actions
Shares
0
Downloads
38
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

JIT-компиляция в виртуальной машине Java (HighLoad++ 2013)

  1. 1. JIT-компиляция в виртуальной машине Java Алексей Рагозин
  2. 2. Обзор доклада Проблемы динамических языков  Виртуальные вызовы  Нетипизированные данные Подходы к JIT компиляции  Method based JIT  Tracing JIT “Грязные трюки” JIT компиляторов
  3. 3. Старый добрый C++ Простое наследование OBJECT VTABLE 00: methodA 01: methodB 02: methodC 03: methodD 010110010010 CODE 101010100110 101010100101 010101001010 101010101010 101010101010 101010101010 101010100010
  4. 4. Старый добрый C++ Множественное наследование OBJECT VTABLE 00: methodA 01: methodB VTABLE 02: methodC 03: methodD 00: methodX 01: methodY 02: methodZ 010110010010 CODE 101010100110 101010100101 010101001010 101010101010 101010101010 101010101010 101010100010 111010100100 011110000010 101001010100
  5. 5. Старый добрый C++ Множественное наследование A B A C D B C D D
  6. 6. Проблемы виртуальных вызовов 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
  7. 7. Проблемы виртуальных вызовов Два обращения в память до перехода • Обращения в память упорядочены • Конвейер процессор блокирован Время доступа к памяти • L1 кэш ~0.5 ns • L2 кэш ~7 ns • RAM ~100 ns
  8. 8. Проблемы динамических типов Поля хранятся как хэш-таблица Доступ к полю • Арифметическая операция • Чтение из памяти • Условная операция • Чтение из памяти
  9. 9. Как сделать быстрый интерпретатор? switch(byteCode) { case STORE: ... case LOAD: ... case ASTORE: ... case ALOADE: ... ... } ?
  10. 10. Как сделать быстрый интерпретатор? Интерпретатор HotSpot JVM • Для каждой инструкции написана процедура на ассемблере • Dispath – jump по адресу из таблицы процедур • Каждая процедура заканчивается jump на dispatch  Интерпретация кода в одном кадре стека  Код и таблица переходов кэшируются  Конвейер процессора остаётся загруженным
  11. 11. Подходы к JIT компиляции Классический подход Независимая компиляция методов + использование динамических оптимизаций Трассирующая компиляция Генерация кода для участков без ветвления + использование гардов
  12. 12. Подходы к JIT компиляции Классический подход Независимая компиляция методов • JVM, V8, Ion Monkey Трассирующая компиляция Генерация кода для участков без ветвления • Flash, Trace Monkey, PyPy, LuaJIT
  13. 13. Трассирующий JIT Интерпретация • Логирование операций и условий ветвления Профилирование • Выявление часто используемых трасс Компиляция трасс • • • • Машинный код без ветвлений Гарды в местах проверки условий Глобальная оптимизация трассы Нарушение гарда – возврат к интерпретации
  14. 14. Трассирующий JIT Достоинства • Девиртуализация и инлайнинг • Сглаживает проблему динамических типов • Глубокая оптимизация горячих участков Недостатки • Трассировка – ОЧЕНЬ медленная интерпретация • Долгое время разогрева
  15. 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
  16. 16. Ссылки 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
  17. 17. HotSpot JVM
  18. 18. HotSpot JVM JIT • Быстрый интерпретатор • Два JIT компилятора (C1 / C2) • Профилирование для управления компиляцией • Деоптимизация кода • On Stack Replacement (OSR)
  19. 19. Девиртуализация Профилирование точек вызовов (call site) • Мономорфный – большинство переходов на одну реализацию • Биморфный - большинство переходов на одну из двух реализаций • Полиморфный
  20. 20. Девиртуализация “Инлайн” кэш переходов if (list.getClass == ArrayList.class) { /* NON VIRTUAL */ list.ArrayList#size() } else { /* VIRTUAL */ list.size(); }
  21. 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. 22. Инкрементальная компиляция  JIT компилирует MyPojo.get() • Collections.binarySort() – инлайнится  Вызовы в Collections.binarySort() становятся мономорфными  JIT продолжает профилирование и перекомпилирует метод  Вызовы get() и compareTo() девиртуализированны и заинлайнены
  23. 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. 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. 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. 26. Сборка мусора и JIT public class Singleton { public static final Singleton INSTANCE = new Singleton() } JIT инлайнит final static переменные • Адрес объекта в памяти в машинном коде • С точки зрения GC код метода структура  Учитывается как корень при маркировке  Адрес в коде корректируется при перемещении объекта
  27. 27. Оптимизация кода “Красивые самолёты летают быстрее” – поговорка авиаконструкторов
  28. 28. Спасибо Алексей Рагозин (alexey.ragozin@gmail.com) http://blog.ragozin.info http://aragozin.timepad.ru

×