Всем привет! Есть такое правило - в логах в ПРОДе не должно быть ничего лишнего. Т.е. с одной стороны логи нужны для хранения стек-трейса исключения и другой полезной при разборе ошибки информации. Есть много инструментов, позволяющих строить по логам метрики, их парсить по заранее настроенной маске, вытаскивая полезную информацию. А с другой стороны логирование, как и любой другой функционал, жрет ресурсы. Как много? Вот так. На картинке показано число обработанных запросов для простейшего контроллера с логированием и без. Причём логирование происходило в RAM диск, т.е не учитываем задержки файловой системы. И шаблон сообщения в лог простой, контекст логирования для тех фреймворков, которые позволяют его настраивать, не используется. Вывод: отказываться от логирования не стоит, асинхрон рулит, а главное про уровни логирования и их настройку на ПРОДе забывать не стоит. Подробнее про условия теста: https://blog.sebastian-daschner.com/entries/logging-performance-comparison #java #logging #performance
Всем привет. Немного про цену создания объектов в Java. Для короткоживущих объектов на последних версиях JVM выигрыш от переиспользования объектов про сравнению с созданием составляет пример 25%. Справедливости ради на Java 8 разница была в 40%, т.е garbage collection развивается. Описание эксперимента тут http://blog.vanillajava.blog/2022/09/java-is-very-fast-if-you-dont-create.html #java #performance
blog.vanillajava.blog
Java is Very Fast, If You Don’t Create Many Objects
You still have to watch how many objects you create. This article looks at a benchmark passing events over TCP/IP at 4 billion events per ...
С другой стороны Java = объекты. Сборщики мусора стали достаточно умными, чтобы запускаться только тогда, когда памяти не хватает, и достаточно быстро убирать короткоживущихе объекты. Можно выбрать сборщик мусора либо с минимальными паузами, либо с минимальным overhead по ресурсам. Про выбор можно почитать тут https://www.baeldung.com/java-choosing-gc-algorithm Перераспределение памяти по ходу работы программы и роста heap можно убрать установив одинаковые xms и xmx, тогда JRE заберёт эту память из системы «навсегда» при старте приложения. Если на сервере много памяти и вы уверены, что heap-а точно хватит на все время работы программы - есть фейковый GC, который имеет ровно одну фичу - падать когда память кончается) https://openjdk.org/jeps/318 Когда это все не работает - примерно на десятках миллионах RPS как в статье из примера выше) #java #gc #performance
Всем привет!
На каких принципах постороены современные высокопроизводительные системы?
Не претендую на полный список, но попробую собрать основные архитектурные принципы с примерами реализующих их систем.
1) shared nothing - каждый запрос на обновление пользовательских данных обрабатывается одним (!) экземпляром сервиса. Пропадает необходимость в распределенных транзакциях или использовании паттерна "Сага", и т.об. повышается скорость и надежность. Технически это горизонтальное масштабирование сервиса\балансировщиков\проксей плюс шардирование хранилища и кэша Примеры: Kafka, Kafka Streams, Spark, Terradata, Hadoop, Solr, ElasticSearch... На примере Kafka: каждый брокер получает свою долю партиций - частей на которые делятся топики - и отвечает за чтение, запись из них, а также репликацию данных. Да, всему кластеру Kafka приходится шарить метаданные о расположении партиций на брокерах - в Zookepper в текущих версиях и в специальных топиках с метаданными в последней версии. И да, ответственный за патрицию может меняться. Но за запросы к пользовательским данным в партиции в каждый момент времени отвечает один брокер, на остальные брокеры эта информация только реплицируется. Репликация проходит асинхронно, без привязки к запросу клиента. Еще примеры: https://dimosr.github.io/shared-nothing-architectures/
2) data locality - данные хранятся на той же ноде, где проходят вычисления. Нет лишних сетевых запросов - быстрее обработка данных. Примеры: Kafka Streams, Spark, Hadoop. На примере Kafka Streams - любые методы, агрегирующие и трансформирующие данные стрима, работают только с данными из тех партиций Kafka, которые лежат на локальной машине. Только так получится добиться приемлемой производительности поточной обработки данных (streaming) в распределенной системе.
3) append-only или log-based storage - данные сохраняются добавлением записи в файл, никаких обновлений и удалений на уровне записей не происходит, файлы ротируются, устаревшие файлы удаляются целиком. Где-то рядом хранится указатель на текущую запись в файле. Т.к последовательная запись на порядок быстрее случайной, то append-only сильно ускоряет запись. Примеры: снова Kafka, Hadoop, Lucene, этот же принцип лежит в основе техник write-ahead logging (WAL) в журналах упреждающей записи СУБД и CQRS + Event Sourcing. Немного о последней: https://www.baeldung.com/cqrs-event-sourcing-java . И о том, как работает WAL https://habr.com/ru/company/postgrespro/blog/459250/ И о том, как Kafka сохраняет данные: https://mbukowicz.github.io/kafka/2020/05/31/how-kafka-stores-messages.html
4) zero-copy - в общем случае данные при чтении из диска и к примеру отправке по сети копируются в памяти несколько раз из буфера в буфер. Почему? Потому что буферы у файлового драйвера, у сетевого драйвера и у Java разные. Но этого можно избежать и работать с данными из буфера ОС, если они не меняются вашим сервисом или меняются, но используются одним процессом. Естественно это ускоряет работу с данными. zero copy должен поддерживаться на уровне ОС, Linux поддерживает. Примеры использования: опять Kafka. Как это работает в Kafka https://andriymz.github.io/kafka/kafka-disk-write-performance/ Про zero copy в Java я упоминал в https://t.me/javaKotlinDevOps/17, вот тут детальнее https://shawn-xu.medium.com/its-all-about-buffers-zero-copy-mmap-and-java-nio-50f2a1bfc05c
to be continued
P.S. Во всех 4 пунктах упоминается Kafka, и это не случайность)
#arch #Kafka #performance
На каких принципах постороены современные высокопроизводительные системы?
Не претендую на полный список, но попробую собрать основные архитектурные принципы с примерами реализующих их систем.
1) shared nothing - каждый запрос на обновление пользовательских данных обрабатывается одним (!) экземпляром сервиса. Пропадает необходимость в распределенных транзакциях или использовании паттерна "Сага", и т.об. повышается скорость и надежность. Технически это горизонтальное масштабирование сервиса\балансировщиков\проксей плюс шардирование хранилища и кэша Примеры: Kafka, Kafka Streams, Spark, Terradata, Hadoop, Solr, ElasticSearch... На примере Kafka: каждый брокер получает свою долю партиций - частей на которые делятся топики - и отвечает за чтение, запись из них, а также репликацию данных. Да, всему кластеру Kafka приходится шарить метаданные о расположении партиций на брокерах - в Zookepper в текущих версиях и в специальных топиках с метаданными в последней версии. И да, ответственный за патрицию может меняться. Но за запросы к пользовательским данным в партиции в каждый момент времени отвечает один брокер, на остальные брокеры эта информация только реплицируется. Репликация проходит асинхронно, без привязки к запросу клиента. Еще примеры: https://dimosr.github.io/shared-nothing-architectures/
2) data locality - данные хранятся на той же ноде, где проходят вычисления. Нет лишних сетевых запросов - быстрее обработка данных. Примеры: Kafka Streams, Spark, Hadoop. На примере Kafka Streams - любые методы, агрегирующие и трансформирующие данные стрима, работают только с данными из тех партиций Kafka, которые лежат на локальной машине. Только так получится добиться приемлемой производительности поточной обработки данных (streaming) в распределенной системе.
3) append-only или log-based storage - данные сохраняются добавлением записи в файл, никаких обновлений и удалений на уровне записей не происходит, файлы ротируются, устаревшие файлы удаляются целиком. Где-то рядом хранится указатель на текущую запись в файле. Т.к последовательная запись на порядок быстрее случайной, то append-only сильно ускоряет запись. Примеры: снова Kafka, Hadoop, Lucene, этот же принцип лежит в основе техник write-ahead logging (WAL) в журналах упреждающей записи СУБД и CQRS + Event Sourcing. Немного о последней: https://www.baeldung.com/cqrs-event-sourcing-java . И о том, как работает WAL https://habr.com/ru/company/postgrespro/blog/459250/ И о том, как Kafka сохраняет данные: https://mbukowicz.github.io/kafka/2020/05/31/how-kafka-stores-messages.html
4) zero-copy - в общем случае данные при чтении из диска и к примеру отправке по сети копируются в памяти несколько раз из буфера в буфер. Почему? Потому что буферы у файлового драйвера, у сетевого драйвера и у Java разные. Но этого можно избежать и работать с данными из буфера ОС, если они не меняются вашим сервисом или меняются, но используются одним процессом. Естественно это ускоряет работу с данными. zero copy должен поддерживаться на уровне ОС, Linux поддерживает. Примеры использования: опять Kafka. Как это работает в Kafka https://andriymz.github.io/kafka/kafka-disk-write-performance/ Про zero copy в Java я упоминал в https://t.me/javaKotlinDevOps/17, вот тут детальнее https://shawn-xu.medium.com/its-all-about-buffers-zero-copy-mmap-and-java-nio-50f2a1bfc05c
to be continued
P.S. Во всех 4 пунктах упоминается Kafka, и это не случайность)
#arch #Kafka #performance
A curious mind
Shared-nothing architectures
An overview of shared-nothing architectures, their pros and cons
10) горизонтальное масштабирование. Поддерживается Kafka (хотя она не является в чистом виде хранилищем), Cassandra, Riak и многими noSQL СУБД. Проблемы: переход с реляционной БД на noSQL не всегда возможен из-за структуры БД, отсутствия опыта работы с noSQL. Кроме того к проблемам несогласованного чтения добавляются проблемы несогласованной записи, они же конфликты записи. Тоже отдельная большая тема.
#storage #performance #jpa
#storage #performance #jpa
Всем привет!
Пару слов про gRPC - новый модный молодежный протокол для интеграции приложений.
Лично я всегда считал основных его преимуществом бинарный формат данных, а, следовательно, быстродействие.
Но это один из трех китов - трех преимуществ gRPC.
Какие же два других?
2) проблема общепринятого сейчас REST-а - в нем не было и нет встроенной схемы данных. Да, есть JSON Schema, OpenAPI и Consumer Driven Contracts. Но где-то они есть, а где-то - нет, причем это могут быть работающие вместе клиент и сервер) Можно же просто получить строку ответа и распарсить ее самостоятельно. И чем больше компания, чем больше у нее микросервисов внутри - тем сложнее будет поддержка и обновление зоопарка REST сервисов со временем. С этим столкнулись Google - разработчик gRPC, Netflix, Dropbox, Facebook - разработчик Thrift, аналога gRPC. В gRPC она есть, из нее генерируется код сервера и клиента. Не весь конечно, сервисная часть - без инфраструктурной и бизнес-логики. Schema first подход, без вариантов)
3) в схему gRPC изначально встроена возможность стриминга. Т.е. можно работать в режиме запрос-ответ, а можно использовать такие комбинации как:
а) запрос - несколько ответов
б) несколько запросов - один ответ
в) двунаправленный стриминг, где логика последовательности запросов и ответов определяется бизнес-процессом.
REST такое не умеет.
Причем схема со стримингом отличается от схемы запроса-ответа буквально одним словом. Код сервера\клиента конечно отличается сильнее)
Из минусов я бы отметил применимость в первую очередь для внутренних взаимодействий, наружу лучше выставлять REST или GraphQL, т.к. потребителям они понятнее. Также могут быть проблемы при изменениях, ломающих обратную совместимость, т.к. из-за бинарности и компактности формата данных жестко зафиксирован порядок полей в запросе\ответе. Возможно где-то будет проблемой то, что gRPC требует HTTP/2, в том же k8s\Openshift траффик HTTP и gRPC нужно разводить по разным портам. Ну и лично меня очень удивляет использование термина Stub в сгенерированном клиенте. Stub и в "боевом" коде... выглядит странно)))
#gRPC #integration #performance
Пару слов про gRPC - новый модный молодежный протокол для интеграции приложений.
Лично я всегда считал основных его преимуществом бинарный формат данных, а, следовательно, быстродействие.
Но это один из трех китов - трех преимуществ gRPC.
Какие же два других?
2) проблема общепринятого сейчас REST-а - в нем не было и нет встроенной схемы данных. Да, есть JSON Schema, OpenAPI и Consumer Driven Contracts. Но где-то они есть, а где-то - нет, причем это могут быть работающие вместе клиент и сервер) Можно же просто получить строку ответа и распарсить ее самостоятельно. И чем больше компания, чем больше у нее микросервисов внутри - тем сложнее будет поддержка и обновление зоопарка REST сервисов со временем. С этим столкнулись Google - разработчик gRPC, Netflix, Dropbox, Facebook - разработчик Thrift, аналога gRPC. В gRPC она есть, из нее генерируется код сервера и клиента. Не весь конечно, сервисная часть - без инфраструктурной и бизнес-логики. Schema first подход, без вариантов)
3) в схему gRPC изначально встроена возможность стриминга. Т.е. можно работать в режиме запрос-ответ, а можно использовать такие комбинации как:
а) запрос - несколько ответов
б) несколько запросов - один ответ
в) двунаправленный стриминг, где логика последовательности запросов и ответов определяется бизнес-процессом.
REST такое не умеет.
Причем схема со стримингом отличается от схемы запроса-ответа буквально одним словом. Код сервера\клиента конечно отличается сильнее)
Из минусов я бы отметил применимость в первую очередь для внутренних взаимодействий, наружу лучше выставлять REST или GraphQL, т.к. потребителям они понятнее. Также могут быть проблемы при изменениях, ломающих обратную совместимость, т.к. из-за бинарности и компактности формата данных жестко зафиксирован порядок полей в запросе\ответе. Возможно где-то будет проблемой то, что gRPC требует HTTP/2, в том же k8s\Openshift траффик HTTP и gRPC нужно разводить по разным портам. Ну и лично меня очень удивляет использование термина Stub в сгенерированном клиенте. Stub и в "боевом" коде... выглядит странно)))
#gRPC #integration #performance
Всем привет!
Сегодня пост с картинками, поэтому ловите https://telegra.ph/Urovni-izolyacii-sovremennogo-Java-prilozheniya-07-29
#java #jvm #docker #performance
Сегодня пост с картинками, поэтому ловите https://telegra.ph/Urovni-izolyacii-sovremennogo-Java-prilozheniya-07-29
#java #jvm #docker #performance
Telegraph
Уровни изоляции современного Java приложения
Всем привет! Уже писал про плюсы Docker - https://t.me/javaKotlinDevOps/165 Но все ли так безоблачно?) Обычно, когда сравнивают Docker с виртуальной машиной (VM) приводят такую схему Из нее видно, что Docker - более легковесное решение по сравнению с VM.…
Всем привет!
Нашёл отличное сравнение скорости конкатенации строк разными методами, от + до StringBuffer, StringBuilder и StringJoiner. И даже есть такая экзотика как String.format со стримами.
Для затравки три интересных факта.
1) StringBuffer даже с синхронизацией существенно быстрее обычной конкатенации.
2) String.format очень(!) медленный.
3) скорость обычной конкатенации с увеличением числа строк растёт экспоненциально.
Подробнее тут https://www.baeldung.com/java-string-concatenation-methods
#java #performance #string
Нашёл отличное сравнение скорости конкатенации строк разными методами, от + до StringBuffer, StringBuilder и StringJoiner. И даже есть такая экзотика как String.format со стримами.
Для затравки три интересных факта.
1) StringBuffer даже с синхронизацией существенно быстрее обычной конкатенации.
2) String.format очень(!) медленный.
3) скорость обычной конкатенации с увеличением числа строк растёт экспоненциально.
Подробнее тут https://www.baeldung.com/java-string-concatenation-methods
#java #performance #string
Baeldung
Performance Comparison Between Different Java String Concatenation Methods | Baeldung
Explore different string concatenation methods in Java and evaluate their performance using JMH.
Всем привет!
Сегодня расскажу про технологию native image.
Стандартная схема работы JVM приложения такая:
1) компилятор превращает исходники в байт-код
2) байт-код запускается на JVM
3) в процессе работы JVM анализирует использование байт-кода и при необходимости оптимизирует его, включая компиляцию в бинарное представление для конкретной процессорной архитектуры. И основные оптимизации надо отметить происходят именно здесь, а не при первичной компиляции. Еще важный момент - классы\библиотеки подгружаются в память не обязательно при старте приложения, а по мере использования. Все это называется JIT - Just in time компиляция. Влиять на нее можно с помощью ряда флагов запуска Java приложения - -server, -client.
Плюс такого подхода - JVM позволяет в 90% случаев игнорировать, на каком железе запускается Java приложение. Минус - долгий старт Java приложения плюс время для "разогрева" и выхода на рабочий режим.
Но с другой стороны с развитием Docker мы и так можем игнорировать особенности железа и ОС на хост-сервере, главное, чтобы там можно было запустить Docker. И наконец кроме долгого старта и разогрева собственно JVM у нас как правило есть Spring с кучей модулей, число которых растет, и в итоге время старта типичного Spring Boot приложения доходит до совсем неприличных величин.
Альтернатива - AOT - Ahead-of-Time compilation. В этом случае мы компилируем исходники в бинарный код в момент первичной компиляции. Причем как собственно приложение, так и JVM и все JAR. Получается такой native image монолит. Проект называется GraalVM https://www.graalvm.org/, официально поддерживается Oracle. Есть open-source версия, основанная на OpenJDK.
Плюс этого подхода - скорость запуска. Это критически важно в облаках, т.к. k8s может "случайно" рестартовать под при изменении конфигурации железа или настроек Deployment. Еще будет выигрыш в скорости обработки запросов, т.к. не тратится CPU и память в runtime на JIT компиляцию.
Какие минусы?
1) невозможна динамическая\ленивая загрузка библиотек\плагинов, classpath фиксируется в момент компиляции. К слову - у этого ограничения есть и плюсы, сложнее эксплуатировать уязвимости типа log4j injection - см. https://t.me/javaKotlinDevOps/4
2) вопрос - откуда компилятор узнает, какой код ему нужно добавить в наш native монолит? Ответ: он идет от метода main. Соответственно, код который явно не вызывается, а, например, вызывается через рефлексию, он не увидит. Соответственно, никакой рефлексии в ПРОМ коде. Что, надо сказать, в целом правильно)
3) аналогично просто так не заработает магия Spring, основанная на рефлексии и динамических прокси. Из чего следует, что мало добавить в Spring приложение AOT компилятор - нужно дорабатывать сам Spring, что и было сделано в Spring Boot 3.2. Другие фреймворки также придется дорабатывать. Например, Mockito до сих пор не работает в native image. Справедливости ради тут причина такая же, как в анекдоте про неуловимого ковбоя Джо - не нужен Mockito в native image)
4) если продолжить про Spring - загрузка бинов по условию: @ConditionalOnProperty, @Profile - тоже не заработает. Нужно указывать при сборке необходимый профиль, чтобы уже при компиляции нужные бины были обнаружены и добавлены в дистрибутив.
5) еще вопрос - но ведь среднее Java приложение + библиотеки + JVM = миллионы строк кода, что будет с компиляцией? Ответ - компиляция будет долгой, до 10 минут на spring boot hello world. Поэтому в документации Spring прямо сказано, что хотя Spring поддерживает запуск тестов в native image - делать так нужно только для интеграционных тестов, лучше на CI, а модульные запускать по старинке, т.к. тут критична скорость получения результата.
#jvm #performance #native_image #spring #docker #buildpacks #cloud #java_start_boost
Сегодня расскажу про технологию native image.
Стандартная схема работы JVM приложения такая:
1) компилятор превращает исходники в байт-код
2) байт-код запускается на JVM
3) в процессе работы JVM анализирует использование байт-кода и при необходимости оптимизирует его, включая компиляцию в бинарное представление для конкретной процессорной архитектуры. И основные оптимизации надо отметить происходят именно здесь, а не при первичной компиляции. Еще важный момент - классы\библиотеки подгружаются в память не обязательно при старте приложения, а по мере использования. Все это называется JIT - Just in time компиляция. Влиять на нее можно с помощью ряда флагов запуска Java приложения - -server, -client.
Плюс такого подхода - JVM позволяет в 90% случаев игнорировать, на каком железе запускается Java приложение. Минус - долгий старт Java приложения плюс время для "разогрева" и выхода на рабочий режим.
Но с другой стороны с развитием Docker мы и так можем игнорировать особенности железа и ОС на хост-сервере, главное, чтобы там можно было запустить Docker. И наконец кроме долгого старта и разогрева собственно JVM у нас как правило есть Spring с кучей модулей, число которых растет, и в итоге время старта типичного Spring Boot приложения доходит до совсем неприличных величин.
Альтернатива - AOT - Ahead-of-Time compilation. В этом случае мы компилируем исходники в бинарный код в момент первичной компиляции. Причем как собственно приложение, так и JVM и все JAR. Получается такой native image монолит. Проект называется GraalVM https://www.graalvm.org/, официально поддерживается Oracle. Есть open-source версия, основанная на OpenJDK.
Плюс этого подхода - скорость запуска. Это критически важно в облаках, т.к. k8s может "случайно" рестартовать под при изменении конфигурации железа или настроек Deployment. Еще будет выигрыш в скорости обработки запросов, т.к. не тратится CPU и память в runtime на JIT компиляцию.
Какие минусы?
1) невозможна динамическая\ленивая загрузка библиотек\плагинов, classpath фиксируется в момент компиляции. К слову - у этого ограничения есть и плюсы, сложнее эксплуатировать уязвимости типа log4j injection - см. https://t.me/javaKotlinDevOps/4
2) вопрос - откуда компилятор узнает, какой код ему нужно добавить в наш native монолит? Ответ: он идет от метода main. Соответственно, код который явно не вызывается, а, например, вызывается через рефлексию, он не увидит. Соответственно, никакой рефлексии в ПРОМ коде. Что, надо сказать, в целом правильно)
3) аналогично просто так не заработает магия Spring, основанная на рефлексии и динамических прокси. Из чего следует, что мало добавить в Spring приложение AOT компилятор - нужно дорабатывать сам Spring, что и было сделано в Spring Boot 3.2. Другие фреймворки также придется дорабатывать. Например, Mockito до сих пор не работает в native image. Справедливости ради тут причина такая же, как в анекдоте про неуловимого ковбоя Джо - не нужен Mockito в native image)
4) если продолжить про Spring - загрузка бинов по условию: @ConditionalOnProperty, @Profile - тоже не заработает. Нужно указывать при сборке необходимый профиль, чтобы уже при компиляции нужные бины были обнаружены и добавлены в дистрибутив.
5) еще вопрос - но ведь среднее Java приложение + библиотеки + JVM = миллионы строк кода, что будет с компиляцией? Ответ - компиляция будет долгой, до 10 минут на spring boot hello world. Поэтому в документации Spring прямо сказано, что хотя Spring поддерживает запуск тестов в native image - делать так нужно только для интеграционных тестов, лучше на CI, а модульные запускать по старинке, т.к. тут критична скорость получения результата.
#jvm #performance #native_image #spring #docker #buildpacks #cloud #java_start_boost
Есть еще ряд интересных моментов. Я расскажу про них на примере Spring Boot native image.
Для борьбы с тем, что часть кода недостижима если идти от точки входа (метод main), есть два инструмента.
1) специальный tracing агент, который можно подключить к приложению, и он будет в runtime логировать такие скрытые вызовы. https://www.graalvm.org/22.3/reference-manual/native-image/metadata/AutomaticMetadataCollection/
2) далее можно создать т.наз. hints - подсказки AOT компилятору, что включить в native image, из того, что он не нашел сам - https://www.graalvm.org/latest/reference-manual/native-image/metadata/ Собственно, большая доля в адаптации фреймворка типа Spring для native image - подготовка таких hints, https://docs.spring.io/spring-boot/docs/3.2.1/reference/html/native-image.html
А что делать если в момент сборки еще не ясно - нужен native image или нет? Или нужны обе версии? Нет проблем - можно совместить оба режима JIT и AOT и создать артефакт, Spring Boot Executable Jar, с байткодом и всеми необходимыми для native image метаданными. И собрать из него native image позже в DevOps pipeline при необходимости.
Для Spring Boot есть два режима сборки. Основной - Native Image Using Buildpacks, в котором в итоге получается docker образ. Для него нужен только Docker на машине-сборщике. И т.наз. Native Build Tools - нужно устанавливать дистрибутив GraalVM, содержащий эти tools, в итоге получается бинарник для железа, на котором происходит сборка.
Итого - штука полезная, но только если вас категорически не устраивает время запуска приложения и все используемые вами фреймворки поддерживают native image.
#jvm #performance #native_image #spring #docker #buildpacks #cloud #startup_time
Для борьбы с тем, что часть кода недостижима если идти от точки входа (метод main), есть два инструмента.
1) специальный tracing агент, который можно подключить к приложению, и он будет в runtime логировать такие скрытые вызовы. https://www.graalvm.org/22.3/reference-manual/native-image/metadata/AutomaticMetadataCollection/
2) далее можно создать т.наз. hints - подсказки AOT компилятору, что включить в native image, из того, что он не нашел сам - https://www.graalvm.org/latest/reference-manual/native-image/metadata/ Собственно, большая доля в адаптации фреймворка типа Spring для native image - подготовка таких hints, https://docs.spring.io/spring-boot/docs/3.2.1/reference/html/native-image.html
А что делать если в момент сборки еще не ясно - нужен native image или нет? Или нужны обе версии? Нет проблем - можно совместить оба режима JIT и AOT и создать артефакт, Spring Boot Executable Jar, с байткодом и всеми необходимыми для native image метаданными. И собрать из него native image позже в DevOps pipeline при необходимости.
Для Spring Boot есть два режима сборки. Основной - Native Image Using Buildpacks, в котором в итоге получается docker образ. Для него нужен только Docker на машине-сборщике. И т.наз. Native Build Tools - нужно устанавливать дистрибутив GraalVM, содержащий эти tools, в итоге получается бинарник для железа, на котором происходит сборка.
Итого - штука полезная, но только если вас категорически не устраивает время запуска приложения и все используемые вами фреймворки поддерживают native image.
#jvm #performance #native_image #spring #docker #buildpacks #cloud #startup_time
www.graalvm.org
GraalVM
GraalVM is an advanced JDK with ahead-of-time Native Image compilation.
Всем привет!
Я уже рассказывал про один из вариантов ускорения запуска JVM приложений - использование native image https://t.me/javaKotlinDevOps/242
Напомню, основная идея была в том, что на этапе компиляции мы превращаем байт-код в нативный код. Можно рассматривать этот процесс как некий дамп универсального кода в конкретный, предназначенный для определенной процессорной архитектуры.
Похожий принцип используется и в случае JVM checkpoint/restore https://openjdk.org/projects/crac/ - проект CRaC.
Проект использует функционал Linux checkpoint/restore для Docker образов https://criu.org/Main_Page.
Т.е. в данном случае мы дампим все содержимое памяти JVM приложения на диск.
Работает, соответственно только для Docker и только в Linux, но кажется это не критическое ограничение.
Вот как это можно сделать на чистом Java приложении https://habr.com/ru/articles/719522/
Есть поддержка на всех основных платформах - Spring Boot, Micronaut, Quarqus, см. https://github.com/CRaC/docs
Проблему долгого первого запуска можно обойти либо сделав дамп до выхода на ПРОМ на идентичном Linux-е, либо разворачивая новые версии как канарейку или в моменты минимальной нагрузки, т.е. когда долгий старт не критичен.
Плюсом этого решения перед native image является то, что нет никаких ограничений на динамическую загрузки библиотек и рефлексию.
Кажется, одним из выгодоприобитетелей будут облачные провайдеры FaaS - Function as a Service, а если быть точным - их пользователи. И, собственно, так и есть - Amazon Lambda уже https://github.com/CRaC/aws-lambda-java-libs подддерживает
#crac #startup_time #jvm #performance #java_start_boost
Я уже рассказывал про один из вариантов ускорения запуска JVM приложений - использование native image https://t.me/javaKotlinDevOps/242
Напомню, основная идея была в том, что на этапе компиляции мы превращаем байт-код в нативный код. Можно рассматривать этот процесс как некий дамп универсального кода в конкретный, предназначенный для определенной процессорной архитектуры.
Похожий принцип используется и в случае JVM checkpoint/restore https://openjdk.org/projects/crac/ - проект CRaC.
Проект использует функционал Linux checkpoint/restore для Docker образов https://criu.org/Main_Page.
Т.е. в данном случае мы дампим все содержимое памяти JVM приложения на диск.
Работает, соответственно только для Docker и только в Linux, но кажется это не критическое ограничение.
Вот как это можно сделать на чистом Java приложении https://habr.com/ru/articles/719522/
Есть поддержка на всех основных платформах - Spring Boot, Micronaut, Quarqus, см. https://github.com/CRaC/docs
Проблему долгого первого запуска можно обойти либо сделав дамп до выхода на ПРОМ на идентичном Linux-е, либо разворачивая новые версии как канарейку или в моменты минимальной нагрузки, т.е. когда долгий старт не критичен.
Плюсом этого решения перед native image является то, что нет никаких ограничений на динамическую загрузки библиотек и рефлексию.
Кажется, одним из выгодоприобитетелей будут облачные провайдеры FaaS - Function as a Service, а если быть точным - их пользователи. И, собственно, так и есть - Amazon Lambda уже https://github.com/CRaC/aws-lambda-java-libs подддерживает
#crac #startup_time #jvm #performance #java_start_boost
Telegram
(java || kotlin) && devOps
Всем привет!
Сегодня расскажу про технологию native image.
Стандартная схема работы JVM приложения такая:
1) компилятор превращает исходники в байт-код
2) байт-код запускается на JVM
3) в процессе работы JVM анализирует использование байт-кода и при необходимости…
Сегодня расскажу про технологию native image.
Стандартная схема работы JVM приложения такая:
1) компилятор превращает исходники в байт-код
2) байт-код запускается на JVM
3) в процессе работы JVM анализирует использование байт-кода и при необходимости…
Всем привет!
Продолжу серию постов https://t.me/javaKotlinDevOps/269 про оптимизацию производительности Java приложения.
В первых двух частях я говорил про такие технологии как:
1) native image - компиляция в нативный код на этапе сборки, т.об. устраняется необходимость class loading-а и JIT компиляции
2) CRaC - сохраняет и восстанавливает состояние работающего Docker образа с JRE на диск, т.об. мы получаем уже оптимизированный код
Какие еще могут быть способы выйти на оптимальную производительность побыстрее? native image мы пока отбрасываем, у нас обычная JVM и на ней запускается байт-код.
Встречный вопрос - а что мешает достижению оптимальной производительности? Как ни странно - JIT компилятор. Ведь чтобы ему понять, как оптимизировать байт-код, нужно собрать статистику. Причем процесс сбора статистики может быть цикличным - собрали, оптимизировали, поняли что оптимизация неверная, вернули байт-код обратно... И это все требует времени. А почему бы тогда не собрать статистику по использованию кода заранее, прихранить ее куда-нибудь, а потом использовать сразу со старта.
Эта техника называется Profile-Guided Optimization, в нее умеет GraalVM https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/PGO/basic-usage/ и упоминаемая ранее Azul JDK https://docs.azul.com/prime/Use-ReadyNow Но к сожалению оба - только в коммерческой версии.
Еще похожую технику использует стандартная OpenJDK при tired compilation https://for-each.dev/lessons/b/-jvm-tiered-compilation но в данном случае речь идет про отпимизацию в течение одной рабочей сессии.
P.S. Это еще не все возможные варианты, не переключайтесь)
P.P.S. Может возникнуть вопрос - зачем GraalVM использует профилирование, он же и так все оптимизировал? Нет, не все. На этапе компиляции нет информации об реальном использовании кода. А оптимизация - это не только компиляция в нативный код, это еще может быть выбрасывание лишних проверок, разворачивание цикла и т.д.
#jre #performance #java_start_boost
Продолжу серию постов https://t.me/javaKotlinDevOps/269 про оптимизацию производительности Java приложения.
В первых двух частях я говорил про такие технологии как:
1) native image - компиляция в нативный код на этапе сборки, т.об. устраняется необходимость class loading-а и JIT компиляции
2) CRaC - сохраняет и восстанавливает состояние работающего Docker образа с JRE на диск, т.об. мы получаем уже оптимизированный код
Какие еще могут быть способы выйти на оптимальную производительность побыстрее? native image мы пока отбрасываем, у нас обычная JVM и на ней запускается байт-код.
Встречный вопрос - а что мешает достижению оптимальной производительности? Как ни странно - JIT компилятор. Ведь чтобы ему понять, как оптимизировать байт-код, нужно собрать статистику. Причем процесс сбора статистики может быть цикличным - собрали, оптимизировали, поняли что оптимизация неверная, вернули байт-код обратно... И это все требует времени. А почему бы тогда не собрать статистику по использованию кода заранее, прихранить ее куда-нибудь, а потом использовать сразу со старта.
Эта техника называется Profile-Guided Optimization, в нее умеет GraalVM https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/PGO/basic-usage/ и упоминаемая ранее Azul JDK https://docs.azul.com/prime/Use-ReadyNow Но к сожалению оба - только в коммерческой версии.
Еще похожую технику использует стандартная OpenJDK при tired compilation https://for-each.dev/lessons/b/-jvm-tiered-compilation но в данном случае речь идет про отпимизацию в течение одной рабочей сессии.
P.S. Это еще не все возможные варианты, не переключайтесь)
P.P.S. Может возникнуть вопрос - зачем GraalVM использует профилирование, он же и так все оптимизировал? Нет, не все. На этапе компиляции нет информации об реальном использовании кода. А оптимизация - это не только компиляция в нативный код, это еще может быть выбрасывание лишних проверок, разворачивание цикла и т.д.
#jre #performance #java_start_boost
Enterprise Craftsmanship
Domain model purity vs. domain model completeness (DDD Trilemma)
I’ve been meaning to write this article for a long time and, finally, here it is: the topic of domain model purity versus domain model completeness.
Всем привет!
Продолжим рассказ про разные способы ускорения Java. Для начала я бы разделил ускорение в целом на 4 более конкретных направления:
1) ускорение запуска приложения за счет оптимизации\отмены первоначальной загрузки классов
2) ускорение выхода приложения на оптимальную производительность путем оптимизации JIT - Just In Time - компиляции байт-кода в нативный
3) ускорение запуска и в какой-то степени выполнения приложения за счет более легковесного фреймворка, используемого для разработки приложения
4) оптимизация сборщика мусора для достижения нужного баланса между затрачиваемыми ресурсами и паузой в обслуживании клиентских запросов, она же Stop the World
Сегодня поговорим про первое направление. С одной стороны упомянутые ранее и native image, и CRaC тоже ускоряют запуск. Но обе технологии имеют ограничения. native image запрещает reflection и динамическую загрузку классов. Образ, сохраненный с помощью CRaC, может содержать что-то лишнее, и с данной технологией нельзя просто так перезапустить приложение при сбое - т.к. возможно причина сбоя лежит в данных, подгруженные из образа.
Начну издалека.
В Java 5 появилась вот такая фича - https://docs.oracle.com/en/java/javase/21/vm/class-data-sharing.html Class-Data Sharing, сокращенно CDS.
Фича появилась и была забыта. Есть такие фичи, про которые все забывают сразу после релиза новой Java) Еще модульность из Java 9 можно вспомнить.
О чем эта фича? Мы записываем в файл метаданные загруженных классов из classpath. Потом этот файл мапился в память работающей JVM. Зачем? Цели было две:
1) расшаривание классов между несколькими инстансами JVM и т.об. уменьшение потребления RAM
2) ускорение запуска (вот оно!)
Вначале фича работала только с классами Java core. Файл с архивом классов Java core входит в состав JDK, найти его можно по имени classes.jsa. Занимает на диске сравнительно немного - 10-15 Мб. И кстати, CDS в Java включена по умолчанию, используется как раз этот файл.
Позже, в Java 10 https://openjdk.org/jeps/310 появилась возможность дампить и пользовательские классы, эту фичу назвали AppCDS. В Java 13 создание архива было упрощено https://openjdk.org/jeps/350
Пользовательские классы можно добавить в архив предварительно запустив процесс со специальной опцией командной строки -XX:ArchiveClassesAtExit
А если у нас Spring? Ребята в Spring 6.1 обратили внимание на данную опцию и добавили ключ командной строки, позволяющий собрать информацию о динамически загружаемых классах именно для Spring Boot приложения https://docs.spring.io/spring-framework/reference/integration/cds.html
А еще дали рекомендации, как максимально точно собрать информацию о классах и подтвердили, что данная опция ускоряет загрузку на ~30% https://spring.io/blog/2023/12/04/cds-with-spring-framework-6-1 Почему подтвердили - именно такую цель ставили разработчики CDS в JEP 310, упомянутом выше.
Итого - идея в чем-то похожа на Profile-Guided Optimization. Только здесь мы предварительно собираем информацию не об использовании кода, а о загруженных классах. Чем больше информации соберем - тем быстрее будет старт приложения. Минусы - версия JDK, Spring и classpath в целом должны совпадать при тестовом прогоне и использовании в ПРОМе.
#jre #performance #spring_boot #spring #java_start_boost
Продолжим рассказ про разные способы ускорения Java. Для начала я бы разделил ускорение в целом на 4 более конкретных направления:
1) ускорение запуска приложения за счет оптимизации\отмены первоначальной загрузки классов
2) ускорение выхода приложения на оптимальную производительность путем оптимизации JIT - Just In Time - компиляции байт-кода в нативный
3) ускорение запуска и в какой-то степени выполнения приложения за счет более легковесного фреймворка, используемого для разработки приложения
4) оптимизация сборщика мусора для достижения нужного баланса между затрачиваемыми ресурсами и паузой в обслуживании клиентских запросов, она же Stop the World
Сегодня поговорим про первое направление. С одной стороны упомянутые ранее и native image, и CRaC тоже ускоряют запуск. Но обе технологии имеют ограничения. native image запрещает reflection и динамическую загрузку классов. Образ, сохраненный с помощью CRaC, может содержать что-то лишнее, и с данной технологией нельзя просто так перезапустить приложение при сбое - т.к. возможно причина сбоя лежит в данных, подгруженные из образа.
Начну издалека.
В Java 5 появилась вот такая фича - https://docs.oracle.com/en/java/javase/21/vm/class-data-sharing.html Class-Data Sharing, сокращенно CDS.
Фича появилась и была забыта. Есть такие фичи, про которые все забывают сразу после релиза новой Java) Еще модульность из Java 9 можно вспомнить.
О чем эта фича? Мы записываем в файл метаданные загруженных классов из classpath. Потом этот файл мапился в память работающей JVM. Зачем? Цели было две:
1) расшаривание классов между несколькими инстансами JVM и т.об. уменьшение потребления RAM
2) ускорение запуска (вот оно!)
Вначале фича работала только с классами Java core. Файл с архивом классов Java core входит в состав JDK, найти его можно по имени classes.jsa. Занимает на диске сравнительно немного - 10-15 Мб. И кстати, CDS в Java включена по умолчанию, используется как раз этот файл.
Позже, в Java 10 https://openjdk.org/jeps/310 появилась возможность дампить и пользовательские классы, эту фичу назвали AppCDS. В Java 13 создание архива было упрощено https://openjdk.org/jeps/350
Пользовательские классы можно добавить в архив предварительно запустив процесс со специальной опцией командной строки -XX:ArchiveClassesAtExit
А если у нас Spring? Ребята в Spring 6.1 обратили внимание на данную опцию и добавили ключ командной строки, позволяющий собрать информацию о динамически загружаемых классах именно для Spring Boot приложения https://docs.spring.io/spring-framework/reference/integration/cds.html
А еще дали рекомендации, как максимально точно собрать информацию о классах и подтвердили, что данная опция ускоряет загрузку на ~30% https://spring.io/blog/2023/12/04/cds-with-spring-framework-6-1 Почему подтвердили - именно такую цель ставили разработчики CDS в JEP 310, упомянутом выше.
Итого - идея в чем-то похожа на Profile-Guided Optimization. Только здесь мы предварительно собираем информацию не об использовании кода, а о загруженных классах. Чем больше информации соберем - тем быстрее будет старт приложения. Минусы - версия JDK, Spring и classpath в целом должны совпадать при тестовом прогоне и использовании в ПРОМе.
#jre #performance #spring_boot #spring #java_start_boost
Oracle Help Center
Java Virtual Machine Guide
This chapter describes the class data sharing (CDS) feature that can help reduce the startup time and memory footprints for Java applications.
Всем привет!
Ну и еще одна оптимизация времени старта Java приложения. Самые внимательные уже могли ее заметить пройдя по ссылкам из предыдущего поста.
С момента появления Spring Boot упаковка приложения в fat jar - jar содержащий все зависимости и Tomcat в придачу (или другой контейнер сервлетов) - стала неким стандартом.
Но fat jar при исполнении требуется распаковать. А разархивация всегда требовала времени, не зря архиваторы используются как бенчмарки для процессорных тестов.
Соответственно, можно заранее разложить зависимости по отдельным файлам для ускорения старта. Вот как рекомендует это делать Spring https://docs.spring.io/spring-boot/reference/packaging/efficient.html
Судя по данным статьи из вчерашнего поста это даст еще 25% ускорения при старте https://spring.io/blog/2023/12/04/cds-with-spring-framework-6-1
#performance #spring #jvm #java_start_boost
Ну и еще одна оптимизация времени старта Java приложения. Самые внимательные уже могли ее заметить пройдя по ссылкам из предыдущего поста.
С момента появления Spring Boot упаковка приложения в fat jar - jar содержащий все зависимости и Tomcat в придачу (или другой контейнер сервлетов) - стала неким стандартом.
Но fat jar при исполнении требуется распаковать. А разархивация всегда требовала времени, не зря архиваторы используются как бенчмарки для процессорных тестов.
Соответственно, можно заранее разложить зависимости по отдельным файлам для ускорения старта. Вот как рекомендует это делать Spring https://docs.spring.io/spring-boot/reference/packaging/efficient.html
Судя по данным статьи из вчерашнего поста это даст еще 25% ускорения при старте https://spring.io/blog/2023/12/04/cds-with-spring-framework-6-1
#performance #spring #jvm #java_start_boost
Enterprise Craftsmanship
Domain model purity vs. domain model completeness (DDD Trilemma)
I’ve been meaning to write this article for a long time and, finally, here it is: the topic of domain model purity versus domain model completeness.
Всем привет!
Ну и последний вариант ускорения старта Java приложения. Самый радикальный, наверное. Отказ от Spring.
Надо отметить, что чистый hello world Spring сервис в плане старта не так уж плох, плюс минус 4 секунды. Основные проблемы начинаются с ростом числа зависимостей. И Spring можно тюнить, подробнее про это можно почитать здесь: https://www.baeldung.com/spring-boot-startup-speed Единственный момент, который мне не понравился - я бы не отключал C2 компиляцию - скорость старта может и увеличится, а вот выйти на оптимальную производительность не получится. И еще интересное исследование - https://github.com/dsyer/spring-boot-allocations Авторы выключили в Spring Boot все, за что мы его любим - Dependency Injection и быструю автоконфигурацию, повесили все на единственный classloader и ускорили старт в 5(!) раз. Только зачем нужен такой Spring?)
Но вернемся к отказу от Spring. Писать на голой Java я не предлагаю) Есть две альтернативы - Quarkus и Micronaut. Оба при создании основной целью ставили получить более быстрый и легковесный фреймворк, чем Spring.
Вот сравнительный бенчмарк Quarkus https://habr.com/ru/companies/haulmont/articles/443242/ Ускорение старта простейшего приложения в 5 раз, до 0.75 секунд. Я беру цифры без native image (GraalVM ), т.к. в этом случае и Spring будет "летать". Для интереса я сравнил локально, разница получилась не в 5 раз, а примерно в 2, с 2.5 до 1.2 секунды. За счет чего получилось ускориться можно почитать тут https://dev.to/nutrymaco/how-quarkus-use-build-time-to-start-your-application-faster-50n Если вкратце - Dependency Injection происходит во время достаточно сложного процесса компиляции.
А вот сравнение Micronaut со Spring https://www.baeldung.com/micronaut-vs-spring-boot Разница чуть поменьше, в 2,5 раза, но тоже ничего) Вот тут, авторы объясняют, почему они быстрее Spring - https://guides.micronaut.io/latest/building-a-rest-api-spring-boot-vs-micronaut-data-gradle-java.html И снова - внедрение зависимостей на этапе компиляции, нет рефлексии и создаваемых в runtime прокси.
Почему я назвал этот вариант самым тяжелым - оба фреймворка сильно отличаются от Spring - по используемым аннотациям, по API в целом. Кроме того они не такие зрелые, им порядка 5-6 лет, поэтому там просто меньше функционала.
#performance #spring #quarkus #micronaut #java_start_boost
Ну и последний вариант ускорения старта Java приложения. Самый радикальный, наверное. Отказ от Spring.
Надо отметить, что чистый hello world Spring сервис в плане старта не так уж плох, плюс минус 4 секунды. Основные проблемы начинаются с ростом числа зависимостей. И Spring можно тюнить, подробнее про это можно почитать здесь: https://www.baeldung.com/spring-boot-startup-speed Единственный момент, который мне не понравился - я бы не отключал C2 компиляцию - скорость старта может и увеличится, а вот выйти на оптимальную производительность не получится. И еще интересное исследование - https://github.com/dsyer/spring-boot-allocations Авторы выключили в Spring Boot все, за что мы его любим - Dependency Injection и быструю автоконфигурацию, повесили все на единственный classloader и ускорили старт в 5(!) раз. Только зачем нужен такой Spring?)
Но вернемся к отказу от Spring. Писать на голой Java я не предлагаю) Есть две альтернативы - Quarkus и Micronaut. Оба при создании основной целью ставили получить более быстрый и легковесный фреймворк, чем Spring.
Вот сравнительный бенчмарк Quarkus https://habr.com/ru/companies/haulmont/articles/443242/ Ускорение старта простейшего приложения в 5 раз, до 0.75 секунд. Я беру цифры без native image (GraalVM ), т.к. в этом случае и Spring будет "летать". Для интереса я сравнил локально, разница получилась не в 5 раз, а примерно в 2, с 2.5 до 1.2 секунды. За счет чего получилось ускориться можно почитать тут https://dev.to/nutrymaco/how-quarkus-use-build-time-to-start-your-application-faster-50n Если вкратце - Dependency Injection происходит во время достаточно сложного процесса компиляции.
А вот сравнение Micronaut со Spring https://www.baeldung.com/micronaut-vs-spring-boot Разница чуть поменьше, в 2,5 раза, но тоже ничего) Вот тут, авторы объясняют, почему они быстрее Spring - https://guides.micronaut.io/latest/building-a-rest-api-spring-boot-vs-micronaut-data-gradle-java.html И снова - внедрение зависимостей на этапе компиляции, нет рефлексии и создаваемых в runtime прокси.
Почему я назвал этот вариант самым тяжелым - оба фреймворка сильно отличаются от Spring - по используемым аннотациям, по API в целом. Кроме того они не такие зрелые, им порядка 5-6 лет, поэтому там просто меньше функционала.
#performance #spring #quarkus #micronaut #java_start_boost
Baeldung
Speed up Spring Boot Startup Time | Baeldung
Learn different configurations and setups that can help decrease Spring Boot startup time.
Всем привет!
И итоговый пост по оптимизации производительности Java. https://telegra.ph/Sravnenie-instrumentov-uskoreniya-Java-servisa-07-08
#java #jre #performance #comparision #java_start_boost
И итоговый пост по оптимизации производительности Java. https://telegra.ph/Sravnenie-instrumentov-uskoreniya-Java-servisa-07-08
#java #jre #performance #comparision #java_start_boost
Enterprise Craftsmanship
Domain model purity vs. domain model completeness (DDD Trilemma)
I’ve been meaning to write this article for a long time and, finally, here it is: the topic of domain model purity versus domain model completeness.
Всем привет!
Один из достаточно частых вопросов на собеседованиях - расскажите про стримы в Java, их плюсы и минусы. Если говорить о минусах - всегда под вопрос ставится быстродействие. У меня давно было желание его сравнить, но как часто бывает - меня опередили.
Вот неплохая статья про быстродействие стримов: https://habr.com/ru/articles/807647/
Какие выводы я сделал:
1) тот факт, что на небольшом объеме данных цикл forEach опережает любые виды стримов - ни о чем, им можно пренебречь. Как минимум в 99% случаев. Мне сложно представить кейс, когда объем данных невелик, но нужно выиграть миллисекунды. Скорее всего эти миллисекунды, или даже десятки миллисекунд, мы потеряем на сетевом взаимодействии. У нас же микросервисы, а это значит много сетевых вызовов. Если говорить о причинах - понятно, что на малых объемах данных накладные расходы, которые конечно же есть у стримов, играют роль. И еще момент - чем проще кусок кода, выполняющийся внутри стрима, тем больше отношение накладных расходов стримов к полезному действию.
2) parallelStream в большинстве случаев бьет forEach на больших объёмах данных. Почему так тоже понятно - эффект распараллеливание становится выше, чем накладные расходы на определенном объеме данных.
Итог: стримы можно использовать как вариант по умолчанию, т.к. они улучшают читаемость кода. В высоконагруженных приложениях\ больших объёмах данных имеет смысл смотреть в сторону parallelStream, особенно если есть результаты нагрузочного тестирования. Ну и только на каких-то критичных участках кода, имея на руках результаты НТ, имеет смысл переписать все на циклы
#streams #performance #interview_question
Один из достаточно частых вопросов на собеседованиях - расскажите про стримы в Java, их плюсы и минусы. Если говорить о минусах - всегда под вопрос ставится быстродействие. У меня давно было желание его сравнить, но как часто бывает - меня опередили.
Вот неплохая статья про быстродействие стримов: https://habr.com/ru/articles/807647/
Какие выводы я сделал:
1) тот факт, что на небольшом объеме данных цикл forEach опережает любые виды стримов - ни о чем, им можно пренебречь. Как минимум в 99% случаев. Мне сложно представить кейс, когда объем данных невелик, но нужно выиграть миллисекунды. Скорее всего эти миллисекунды, или даже десятки миллисекунд, мы потеряем на сетевом взаимодействии. У нас же микросервисы, а это значит много сетевых вызовов. Если говорить о причинах - понятно, что на малых объемах данных накладные расходы, которые конечно же есть у стримов, играют роль. И еще момент - чем проще кусок кода, выполняющийся внутри стрима, тем больше отношение накладных расходов стримов к полезному действию.
2) parallelStream в большинстве случаев бьет forEach на больших объёмах данных. Почему так тоже понятно - эффект распараллеливание становится выше, чем накладные расходы на определенном объеме данных.
Итог: стримы можно использовать как вариант по умолчанию, т.к. они улучшают читаемость кода. В высоконагруженных приложениях\ больших объёмах данных имеет смысл смотреть в сторону parallelStream, особенно если есть результаты нагрузочного тестирования. Ну и только на каких-то критичных участках кода, имея на руках результаты НТ, имеет смысл переписать все на циклы
#streams #performance #interview_question
Хабр
Еще раз о перформансе стримов в Java
Время от времени я наблюдаю или даже бываю втянутым в спор о перформансе стримов в джаве. Общеизвестно, что стримы это компромисс между перформансом и удобством. Однако я не нашел вменяемого набора...