Оптимизация доступа к загрузчикам классов, определяемых пользователем JVM. Часть 2.

Создать новую статью

retweet
15.08.2010 10:00


Аннотация

Из статьи вы узнаете о проекте «Оптимизация доступа к загрузчикам классов, определяемых пользователем JVM», которым мы занимались в рамках Летней школы Intel 2010. Цель проекта заключалась в оптимизации механизма определения загрузчика класса в Java Virtual Machine, который стал «бутылочным горлышком», негативно влияющим на производительность приложений, активно использующих механизм сериализации при работе на многоядерных системах.

Введение

Язык Java позволяет пользователю переопределять загрузчики классов, что является аспектом одной из основных особенностей этого языка – динамической загрузки классов. Эта возможность достаточно популярна у программистов и, в частности, занимает существенное место в популярном тесте производительности SPECjvm2008. Однако инфраструктура HotSpot JVM, предоставляющая доступ к пользовательским загрузчикам классов, оказалась не готова к современным архитектурам процессоров, когда количество ядер в одиночном сервере может достигать 64-х и более. Она стала «бутылочным горлышком», существенно ограничивая производительность приложений, интенсивно использующих этот механизм. Задачей является оптимизация доступа к пользовательским загрузчикам классов и как следствие радикальное увеличение произодительности теста SPECjvm2008.serial на платформе Nehalem-EX.

Успешная реализация проекта обеспечит повышение производительности процессоров Intel в популярном тесте SPECjvm2008 и поможет упрочить лидерство Intel в сегменте серверных платформ.

Моим напарником на данной задаче был Владимир Костюков. Менеджером – Вячеслав Шакин, а менторами – Алексей Шпиталёв и Николай Сидельников.

Немного истории...

Впервые проблема была замечена при сравнении результатов запуска теста SPECjvm2008.serial на платформе Dunnington для различного количества потоков:

Таблица 1. Зависимость количества очков на тесте от количества потоков

Количество потоков Количество "очков"
6 136
12 244
16 275
18 281
24 282

 

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

При просмотре верхушки стека одного из рабочих потоков подозрение вызвал native метод, использующий достаточно емкие по времени функции JVM. Этим методом оказался private static native ClassLoader latestUserDefinedLoader() класса ObjectInputStream служащий для определения объекта-загрузчика, необходимого для загрузки класса десериализуемого объекта. Замечание: Под "емкими по времени" подразумеваются методы, в которых система проводит большое количество времени. Временные затраты на каждую функцию были определены с помощью Intel® VTune™. Для проверки гипотезы о том, что данный метод оказывает отрицательное влияние на производительность и масштабируемость, был проведен эксперимент. Значение, возвращаемое методом, было закэшировано, что позволило вызывать метод только один раз. Экспериментальное API показало прирост производительности.

Причины проблемы

Для понимания причин проблем с производительностью посмотрим внимательнее на наш «проблемный» метод:

JVM_ENTRY(jobject, JVM_LatestUserDefinedLoader(JNIEnv *env)) 
  for (vframeStream vfst(thread); !vfst.at_end(); vfst.next()) {
    // UseNewReflection
    vfst.skip_reflection_related_frames(); // Only needed for 1.4 reflection
    klassOop holder = vfst.method()->method_holder();
    oop loader = instanceKlass::cast(holder)->class_loader();
    if (loader != NULL) {
      return JNIHandles::make_local(env, loader);
    }
  }
  return NULL;
JVM_END

Данный метод служит для определения загрузчика, которым следует загружать десериализуемый класс. Самой затратной частью в данном методе оказался вызов vfst.next(), который используется для итерирования по фреймам стека исполнения. Подробное описание проблемы можно прочитать в статье моего напарника Владимира Костюкова Оптимизация доступа к загрузчикам классов, определенных пользователем JVM. Часть 1.

Пути решения проблемы

Существует, как минимум, два узких места/проблемы в описанном выше механизме получения последнего ненулевого загрузчика на стеке исполнения. Во-первых, сам по себе нативный вызов JVM достаточно дорогой по отношению к вызовам в JRE. Во-вторых - высота стека, а следовательно и количество итераций цикла - относительно большие величины на момент вызова метода JVM_LatestUserDefinedLoader(). Первая проблема решается с помощью кеширования загрузчика в JRE. Вторая - переносом точки вызова метода JVM_LatestUserDefinedLoader() в метод readObject().

Реализацией решения стал следующий патч класса ObjectInputStream:

…
private ClassLoader cachedUserDefinedLoader;
…
public final Object readObject() throws IOException, ClassNotFoundException 
{
+ // caching caller class loader if and only if is non-recursive calling
+ if (curContext == null) {
+ cachedUserDefinedLoader = latestUserDefinedLoader();
+ }
…
}
protected Class<?> resolveClass(String classname) throws IOException, ClassNotFoundException 
{
- return Class.forName(name, false, latestUserDefinedLoader());
+ return Class.forName(name, false, cachedUserDefinedLoader == null 
+ ? (cachedUserDefinedLoader = latestUserDefinedLoader()) 
+ : cachedUserDefinedLoader);
}

Результаты применения патча API

Было произведено измерение производительности HotSpot JVM c оригинальным и измененным API на платформах Nehalem-EX и Dunnington следующих конфигураций:

Nehalem-EX Server: 2.27 GHz, 8 процессоров по 8 ядер в каждом, 128 GB DDR3-1067

Dunnington Server : 2.27 GHz, 4 процессора по 6 ядер в каждом, 32 GB DDR2-667

Для проведения тестирования были использованы параметры, соответствующие конфигурациям платформ:

  • Максимальное количество потоков было равным количеству ядер соответствующей машины.
  • Операционная система: RHEL5.3 2.6.18-128.el5 x86_64 GNU/Linux
  • Параметры запуска теста SPECjvm2008: 120 секунд на warmup ("разогрев" виртуальной машины), и измерение производительности в течение 240 секунд
  • Версия JVM: Sun JDK6u20P, x64 Linux X64
  • Дополнительные параметры JVM, касающиеся оперативной памяти:
    • Nehalem-EX: -Xmn56g –Xms64g –Xmx64g
    • Dunnington: -Xmn12g -Xms16g -Xmx16g

Запуски теста производились по три раза, и, затем, из трех результатов выбирался наибольший. Для расчета ускорения (% Boost) была использована следующая формула:

% Boost = (|OriginalScores - PatchedScores|)/OriginalScores

где OriginalScores - результаты теста на оригинальном Java API
PatchedScores - результаты теста на патченном Java API

На платформе Nehalem-EX тесты запускались на различном количестве потоков: от 8 до 64 с шагом 4. На графике мы видим значительное увеличение производительности, по сравнению с оригинальным API при увеличении количества потоков. Итоговое ускорение составило 28 % на максимальном количестве – 64 потока. Максимальное же ускорение, было получено на 32 потоках и составило 48% !

Таблица 2. Результаты тестирования на платформе Nehalem

Количество потоков
Оригинальное API Измененное API Ускорение, %
8 234 264 13
12 349 393 13
16 469 523 12
20 532 629 18
24 606 734 21
28 620 838 35
32 634 941 48
36 674 992 47
40 714 1032 45
44 740 1045 41
48 772 1079 40
52 794 1127 42
56 894 1166 30
60 924 1184 28
64 939 1201 28

График показателей теста на платформе Nehalem-EX

Рисунок 1. График показателей теста на платформе Nehalem-EX

На платформе Dunnington был получен максимальный прирост производительности 11%, но на максимальном количестве потоков прирост был 2% (Таблица 3).

Таблица 3. Результаты тестирования на платформе Dunnington
Количество потоков
Оригинальное API Измененное API Ускорение, %
24 284 289 2

 

Была проанализирована статистика вызовов при выполнении теста serial, в результате чего было определено, что:

Во-первых, количество вызовов функции сократилось в 2.5 раза (40000 против 16000);

Во-вторых, высота стека, вглубь которого необходимо пройти функции, уменьшилась в 4-6 раз (12-20 против 3).

Различия количества вызовов для оригинального и измененного API

Рисунок 2. Различия количества вызовов для оригинального и измененного API


Различия высоты стека для оригинального и измененного API

Рисунок 3. Различия высоты стека для оригинального и измененного API

Дополнительно запускался тест protobuf-compare, который сравнивает производительность различных библиотек Java для сериализации. На диаграммах (Рисунок 4, 5) показано общее время выполнения теста на различных библиотеках. Причем меньшее значение соответствует большей скорости. Данный тест запускался на платформе Nehalem.

protobuf-compare на оригинальном API

Рисунок 4. Результаты запуска protobuf-compare на оригинальном API

protobuf-compare на измененном API

Рисунок 5. Результаты запуска protobuf-compare на измененном API

Мы видим, что время выполнения теста стандартной сериализации (самая нижняя строка на диаграммах) сократилось на 18 % (Таблица 4), на результаты остальных тестов изменение API не оказало значительного влияния.

Таблица 4. Результаты выполнения теста protobuf-compare

Оригинальное API
Измененное API Ускорение, % API
82479 сек. 67437 сек. 18

 

Для обеспечения некоторой уверенности, что патч не нарушает функционирование платформы Java был использован Java Compatibility Kit 6b. Конечно, нельзя сказать, что проверка на данной группе тестов гарантирует полную корректность предложенного изменения API, но задача гарантирования корректности не входила в наши планы.

Результаты работы

В результате проделанной работы было достигнуто увеличение производительности теста SPECjvm2008 на платформе Nehalem на 28% при 64 потоках, максимальный прирост составил 48%. Патч Java API был предложен для рассмотрения в Oracle.

Все это свидетельствует о том, что поставленные цели были достигнуты, и задача выполнена на 100%.

Впечатления от стажировки

И, напоследок, конечно же впечатления от Intel Summer School 2010!

Я уже второй раз участвую в Летней Школе Intel, и надо сказать, что эти две летние школы кардинально различаются по своей организации. В прошлом году мы работали в лаборатории Intel НГУ и вся корпоративная жизнь обошла нас, что говорится, стороной. В этом же году нам посчастливилось посмотреть на Intel изнутри. Работа внутри корпорации оставила только позитивные впечатления. Организация работы в офисе находится на очень высоком уровне, а дружелюбный коллектив и удобные рабочие места способствовали плодотворной работе. В начале нас «напугали» строгостью корпоративной политики безопасности, но на самом деле выполнять эти требования оказалось достаточно просто. Конечно же, мне не хватало моих любимых сервисов Google и операционной системы Ubuntu Linux, но к неудобствам в виде Microsoft Outlook и Microsoft Windows быстро привыкаешь :)

В рамках Летней Школы мы как работали над проектами, так и посещали образовательные тренинги и вебинары, благодаря которым я открыл для себя много нового и значительно повысил свой профессиональный уровень. Проведение вебинаров – это совершенно новый опыт образовательных мероприятий, и, конечно, не сразу все проходило гладко, но организаторы успешно справились со всеми сложностями. Интересно было побывать на телефонном совещании с сотрудниками из зарубежных офисов, на котором мы с Владимиром представляли результаты нашей работы. Конечно, было сложно воспринимать совещание на английском языке в обстановке постоянных шумов, но все завершилось хорошо и зарубежные коллеги оценили наши достижения. Также, во время стажировки, мне (уже во второй раз) посчастливилось побывать в Центре компетенции СО РАН – Intel по высокопроизводительным вычислениям. Мне уже была знакома программа экскурсии с прошлого года, но в этом году нам показали реальные результаты научных вычислений на суперкомпьютерах.

Не могу не упомянуть и про развлекательную программу. За эти короткие 2 месяца мы поучаствовали в праздновании шестилетия офиса, и даже победили на импровизированных олимпийских играх:) Успели побывать на множестве Дней Рождений сотрудников, на которые принято приглашать всех друзей. И, в общем, замечательно провели время.

Благодарю всех организаторов Летней Школы, и, конечно же,выражаю особую благодарность Николаю Куртову как директору Новосибирской школы. А также нашим с Владимиром менторам – Алексею и Николаю, постоянно помогавшим нам с проектом.