[Python, Java] Удав укрощает Graal VM
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В мире Java за последнее время произошло много интересных событий. Одним из таких событий стал выход первой production ready версии Graal VM.
Лично у меня Graal давно вызывает нескрываемый интерес и я пристально слежу за докладами и последними новостями в этой области. Одно время попался на глаза доклад Криса Талингера. В нём Крис рассказывает как в Twitter удалось получить значительный выигрыш в производительности, применив для настройки Graal алгоритмы машинного обучения. У меня появилось стойкое желание попробовать подобное самому. В этой статье хочу поделится тем, что в итоге получилось.
Эксперимент
Для реализации эксперимента мне понадобились:
- cвежий Graal VM Community Edition. На момент написания статьи это 20.2.0
- выделенное облачное окружение для нагрузочного тестирования
- NewRelic для сбора метрик
- генератор тестовой нагрузки
- программа на Python и набор скриптов, для реализации самого алгоритма ML
Если описать задачу сухим языком математики, то она будет выглядеть так
Найти такие значения параметров $inline$A = (a_1,a_2,..,a_n)$inline$ при которых функция
$inline$f(x_1,x_2,..,x_n)$inline$ принимает максимальное значение.
Я решил минимизировать потребление процессорного времени и выбрал такую целевую функцию:
$$display$$f=1/mean(CPUUtilization)$$display$$
Чем меньше нагружен процессор, тем ближе целевая функция к единице.
В качестве параметров которые нужно найти взял те же, что и в докладе:
-Dgraal.MaximumInliningSize -Dgraal.TrivialInliningSize -Dgraal.SmallCompiledLowLevelGraphSize
Все они отвечают за инлайнинг. Это важная оптимизация, которая позволяет
сэкономить на вызове методов.
С постановкой задачи оптимизации разобрались. Теперь пришло время пройтись по шагам
алгоритма оптимизации:
- алгоритм делает предположение о том какие параметры оптимальные
- меняет конфигурацию JVM и запускает нагрузочный тест
- снимает метрики и вычисляет значение целевой функции
- делает новое предположение на основе значения целевой функции
Процесс повторяется несколько раз.
В качестве алгоритма поиска Twitter предлагает использовать байесовскую оптимизацию. Она лучше справляется с зашумленными функциями и не требует большого количества итераций.
Байесовская оптимизация работает путем построения апостериорного распределения функций,
которое наилучшим образом её описывает. По мере роста количества наблюдений улучшается апостериорное распределение и алгоритм становится более определённым в том, какие регионы в пространстве параметров стоит изучить.
Получение метрик из NewRelic
Для работы с NewRelic REST API необходимо узнать свои APP_ID и API_KEY.
APP_ID — это уникальный идентификатор приложения в системе. Его можно найти в разделе APM.
API_KEY необходимо создать или узнать из настроек профиля в NewRelic.
Структура ответа для всех метрик приблизительно одинакова и имеет следующий вид:
{
"metric_data": {
"from": "time",
"to": "time",
"metrics_not_found": "string",
"metrics_found": "string",
"metrics": [
{
"name": "string",
"timeslices": [
{
"from": "time",
"to": "time",
"values": "hash"
}
]
}
]
}
}
В итоге метод для получения метрик будет таким:
def request_metrics(params):
app_id = "APP_ID"
url = "https://api.newrelic.com/v2/applications/"+ app_id + "/metrics/data.json"
headers = {'X-Api-Key':"API_KEY"}
response = requests.get(url, headers=headers, params=params)
return response.json()
Для получения CPU Utilzation значение params следующее:
params = {
'names[]': "CPU/User/Utilization",
'values[]': "percent",
'from': timerange[0],
'to': timerange[1],
'raw': "false"
}
timerange хранит значения времени начала и конца нагрузочного теста.
Далее парсим запрос и извлекаем необходимые метрики
def get_timeslices(response_json, value_name):
metrics = response_json['metric_data']['metrics'][0]
timeslices = metrics['timeslices']
values = []
for t in timeslices:
values.append(t['values'][value_name])
return values
Алгоритм оптимизации
Перейдем к самому интересному — поиску оптимальных параметров.
Для реализации байесовской оптимизации взял уже готовую библиотеку BayesianOptimization.
Сначала создадим метод для вычисления целевой функции.
def objective_function(maximumInliningSize, trivialInliningSize, smallCompiledLowLevelGraphSize):
update_config(int(maximumInliningSize), int(trivialInliningSize), int(smallCompiledLowLevelGraphSize))
timerange = do_test()
data = get_results(timerange)
return calculate(data)
Метод _updateconfig вызывает скрипт, который обновляет конфиг приложения. Далее в _dotest происходит вызов скрипта для запуска нагрузочного теста.
Каждое изменение конфигурации требует перезапуска JVM и первые несколько минут идёт фаза прогрева. Эту фазу необходимо отфильтровать, откинув первые минуты.
В методе calculate вычисляем целевую функцию:
value = 1 / (mean(filtered_data))
Необходимо ограничить поиск
pbounds = {
'maximumInliningSize': (200, 500),
'trivialInliningSize': (10, 25),
'smallCompiledLowLevelGraphSize': (200, 650)
}
Так как улучшение должно быть относительно настроек по умолчанию, то я добавил соответствующую точку
optimizer.probe(
params={"maximumInliningSize": 300.0,
"trivialInliningSize": 10.0,
"smallCompiledLowLevelGraphSize": 300.0},
lazy=True,
)
Окончательный метод ниже
def autotune():
pbounds = {
'maximumInliningSize': (200, 500),
'trivialInliningSize': (10, 25),
'smallCompiledLowLevelGraphSize': (200, 650)
}
optimizer = BayesianOptimization(
f=objective_function,
pbounds=pbounds,
random_state=1,
)
optimizer.probe(
params={"maximumInliningSize": 300.0,
"trivialInliningSize": 10.0,
"smallCompiledLowLevelGraphSize": 300.0},
lazy=True,
)
optimizer.maximize(
init_points=2,
n_iter=10,
)
print(optimizer.max)
В данном примере _objectivefunction выполнится 12 раз и в конце выведет значение
параметров, при которых наша целевая функция была максимальная. Чем больше итераций, то тем точнее мы приближаемся к максимуму функции.
При необходимости все итерации можно вывести вот так:
for i, res in enumerate(optimizer.res):
print("Iteration {}: \n\t{}".format(i, res))
Код выведет значения целевой функции и параметров для каждой итерации.
Iteration 0:
{'target': 0.02612330198537095, 'params': {'maximumInliningSize': 300.0, 'smallCompiledLowLevelGraphSize': 300.0, 'trivialInliningSize': 10.0}}
Iteration 1:
{'target': 0.02666666666666667, 'params': {'maximumInliningSize': 325.1066014107722, 'smallCompiledLowLevelGraphSize': 524.1460220489712, 'trivialInliningSize': 10.001715622260173}}
...
Результаты
Сравнил два прогона нагрузочного теста с настройками по умолчанию и вычисленными в ходе эксперимента.
На графике можно заметить, что CPU Utilization уменьшился для случая Graal
Среднее время отклика также незначительно снизилось:
Пропускная способность ограничена сверху и никак не менялась для обоих прогонов.
Заключение
В итоге удалось получить снижение нагрузки CPU в среднем на 4-5%.
Для нашего проекта такая экономия на CPU не существенна, но для proof of concept
результат достаточно неплохой.
С2 много лет оптимизировали под Java и поэтому соревноваться Graal с С2 пока сложно. Больше выгоды можно получить от связки Graal с другими JVM языками, такими как Scala и Kotlin.
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, JavaScript, Программирование] Заметка о перебираемых объектах
- [Java] Модульное тестирование, детальное рассмотрение параметризованных тестов. Часть I
- [Разработка под Android, Kotlin] Solving coding problems with Kotlin: Collection functions
- [Виртуализация, История IT, Старое железо] Истоки виртуализации (перевод)
- [Информационная безопасность, Oracle] Oracle выпустила экстренный патч для WebLogic
- [Python, Программирование, Git, GitHub, Учебный процесс в IT] 25 лучших репозиториев GitHub для разработчиков Python (перевод)
- [Python, Машинное обучение, Социальные сети и сообщества] Определение токсичных комментариев на русском языке
- [Разработка веб-сайтов, JavaScript, Программирование] Управление памятью в JavaScript (перевод)
- [Разработка веб-сайтов, Google API, IT-компании] Систему Google reCAPTCHA раскритиковали за приватность
- [Python] Making python's dream of multithreading come true
Теги для поиска: #_python, #_java, #_graalvm, #_python, #_performance_optimization, #_python, #_java
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 22:51
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В мире Java за последнее время произошло много интересных событий. Одним из таких событий стал выход первой production ready версии Graal VM. Лично у меня Graal давно вызывает нескрываемый интерес и я пристально слежу за докладами и последними новостями в этой области. Одно время попался на глаза доклад Криса Талингера. В нём Крис рассказывает как в Twitter удалось получить значительный выигрыш в производительности, применив для настройки Graal алгоритмы машинного обучения. У меня появилось стойкое желание попробовать подобное самому. В этой статье хочу поделится тем, что в итоге получилось. Эксперимент Для реализации эксперимента мне понадобились:
Если описать задачу сухим языком математики, то она будет выглядеть так Найти такие значения параметров $inline$A = (a_1,a_2,..,a_n)$inline$ при которых функция $inline$f(x_1,x_2,..,x_n)$inline$ принимает максимальное значение. Я решил минимизировать потребление процессорного времени и выбрал такую целевую функцию: $$display$$f=1/mean(CPUUtilization)$$display$$ Чем меньше нагружен процессор, тем ближе целевая функция к единице. В качестве параметров которые нужно найти взял те же, что и в докладе: -Dgraal.MaximumInliningSize -Dgraal.TrivialInliningSize -Dgraal.SmallCompiledLowLevelGraphSize
Все они отвечают за инлайнинг. Это важная оптимизация, которая позволяет сэкономить на вызове методов. С постановкой задачи оптимизации разобрались. Теперь пришло время пройтись по шагам алгоритма оптимизации:
Процесс повторяется несколько раз. В качестве алгоритма поиска Twitter предлагает использовать байесовскую оптимизацию. Она лучше справляется с зашумленными функциями и не требует большого количества итераций. Байесовская оптимизация работает путем построения апостериорного распределения функций, которое наилучшим образом её описывает. По мере роста количества наблюдений улучшается апостериорное распределение и алгоритм становится более определённым в том, какие регионы в пространстве параметров стоит изучить. Получение метрик из NewRelic Для работы с NewRelic REST API необходимо узнать свои APP_ID и API_KEY. APP_ID — это уникальный идентификатор приложения в системе. Его можно найти в разделе APM. API_KEY необходимо создать или узнать из настроек профиля в NewRelic. Структура ответа для всех метрик приблизительно одинакова и имеет следующий вид: {
"metric_data": { "from": "time", "to": "time", "metrics_not_found": "string", "metrics_found": "string", "metrics": [ { "name": "string", "timeslices": [ { "from": "time", "to": "time", "values": "hash" } ] } ] } } В итоге метод для получения метрик будет таким: def request_metrics(params):
app_id = "APP_ID" url = "https://api.newrelic.com/v2/applications/"+ app_id + "/metrics/data.json" headers = {'X-Api-Key':"API_KEY"} response = requests.get(url, headers=headers, params=params) return response.json() Для получения CPU Utilzation значение params следующее: params = {
'names[]': "CPU/User/Utilization", 'values[]': "percent", 'from': timerange[0], 'to': timerange[1], 'raw': "false" } timerange хранит значения времени начала и конца нагрузочного теста. Далее парсим запрос и извлекаем необходимые метрики def get_timeslices(response_json, value_name):
metrics = response_json['metric_data']['metrics'][0] timeslices = metrics['timeslices'] values = [] for t in timeslices: values.append(t['values'][value_name]) return values Алгоритм оптимизации Перейдем к самому интересному — поиску оптимальных параметров. Для реализации байесовской оптимизации взял уже готовую библиотеку BayesianOptimization. Сначала создадим метод для вычисления целевой функции. def objective_function(maximumInliningSize, trivialInliningSize, smallCompiledLowLevelGraphSize):
update_config(int(maximumInliningSize), int(trivialInliningSize), int(smallCompiledLowLevelGraphSize)) timerange = do_test() data = get_results(timerange) return calculate(data) Метод _updateconfig вызывает скрипт, который обновляет конфиг приложения. Далее в _dotest происходит вызов скрипта для запуска нагрузочного теста. Каждое изменение конфигурации требует перезапуска JVM и первые несколько минут идёт фаза прогрева. Эту фазу необходимо отфильтровать, откинув первые минуты. В методе calculate вычисляем целевую функцию: value = 1 / (mean(filtered_data))
Необходимо ограничить поиск pbounds = {
'maximumInliningSize': (200, 500), 'trivialInliningSize': (10, 25), 'smallCompiledLowLevelGraphSize': (200, 650) } Так как улучшение должно быть относительно настроек по умолчанию, то я добавил соответствующую точку optimizer.probe(
params={"maximumInliningSize": 300.0, "trivialInliningSize": 10.0, "smallCompiledLowLevelGraphSize": 300.0}, lazy=True, ) Окончательный метод ниже def autotune():
pbounds = { 'maximumInliningSize': (200, 500), 'trivialInliningSize': (10, 25), 'smallCompiledLowLevelGraphSize': (200, 650) } optimizer = BayesianOptimization( f=objective_function, pbounds=pbounds, random_state=1, ) optimizer.probe( params={"maximumInliningSize": 300.0, "trivialInliningSize": 10.0, "smallCompiledLowLevelGraphSize": 300.0}, lazy=True, ) optimizer.maximize( init_points=2, n_iter=10, ) print(optimizer.max) В данном примере _objectivefunction выполнится 12 раз и в конце выведет значение параметров, при которых наша целевая функция была максимальная. Чем больше итераций, то тем точнее мы приближаемся к максимуму функции. При необходимости все итерации можно вывести вот так: for i, res in enumerate(optimizer.res):
print("Iteration {}: \n\t{}".format(i, res)) Код выведет значения целевой функции и параметров для каждой итерации. Iteration 0:
{'target': 0.02612330198537095, 'params': {'maximumInliningSize': 300.0, 'smallCompiledLowLevelGraphSize': 300.0, 'trivialInliningSize': 10.0}} Iteration 1: {'target': 0.02666666666666667, 'params': {'maximumInliningSize': 325.1066014107722, 'smallCompiledLowLevelGraphSize': 524.1460220489712, 'trivialInliningSize': 10.001715622260173}} ... Результаты Сравнил два прогона нагрузочного теста с настройками по умолчанию и вычисленными в ходе эксперимента. На графике можно заметить, что CPU Utilization уменьшился для случая Graal Среднее время отклика также незначительно снизилось: Пропускная способность ограничена сверху и никак не менялась для обоих прогонов. Заключение В итоге удалось получить снижение нагрузки CPU в среднем на 4-5%. Для нашего проекта такая экономия на CPU не существенна, но для proof of concept результат достаточно неплохой. С2 много лет оптимизировали под Java и поэтому соревноваться Graal с С2 пока сложно. Больше выгоды можно получить от связки Graal с другими JVM языками, такими как Scala и Kotlin. =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 22:51
Часовой пояс: UTC + 5