Pull to refresh

Еще немного о производительности Java

Reading time5 min
Views21K
“Есть ложь большая, есть маленькая, а есть статистика производительности Java.”

В последнее время я стал замечать обилие тестов производительности Java с удивительно невероятным разбросом результатов тестирования. Результаты отличаются с точностью до полной противоположности, в зависимости от того кто проводит тесты.

Многим понятно, что такие тесты это часть маркетинга. И те кто его заказывает, заказывают и результат. Но удивительность ситуации в том, что упрекнуть во лжи некого. Нельзя отказать в истинности тех тестов в которых Java на порядок уступает C#, ни тем тестам в которых Java превосходит С++ (не Си). Проблема в том, что такие тесты ориентированны в основном на начинающих разработчиков с целью переманить в свой стан побольше программистов и тем самым увеличить коммерческую привлекательность платформы для бизнеса. И в таких тестах намеренно упускаются сложные для понимания подробности работы динамической JIT компиляции и адаптивной оптимизации HotSpot, в которые начинающие программисты редко вдаются.

Чем это чревато? Банальным когнитивным диссонансом.

Когда-то я тоже писал сравнительный тест программы на Delphi и Java. И получил чуть ли не десятикратное отставание производительности Java. Конечно можно было списать все на свои кривые руки, поэтому я попытался разобраться с механизмом JIT компиляции. Ведь если дело не в руках, и действительно Java создает такой неэффективный нативный код, то от такой платформы нужно держаться как можно дальше. Но реальность говорит, что это один из самых распространенных промышленных языков программирования, а бизнес с подобными потерями производительности мириться не будет.

Попробую на пальцах объяснить как я понял суть того, что происходит в недрах Java, и если я в чем-то ошибся, то прошу меня поправить. Так как, не смотря на упорность, суть некоторых процессов осталась для меня загадкой.

Начнем с того, что адаптивная JIT компиляция довольная накладная процедура, и компилировать весь код сразу слишком дорого. Да это и не нужно. По статистике работы промышленных приложений 80% времени программы проводят в 20% кода. Именно эти 20% выгоднее всего скомпилировать. Остальной код не окажет на производительность существенного эффекта даже если его исполнить в режиме интерпретации. Для того чтобы вычислить эти 20% нужно хорошенько изучить алгоритм. А для изучения всех алгоритмов HotSpot требуется сбор значительной статистики, поэтому в начале вся программа работает в режиме интерпретации.

По мере накопления статистики компилятор компилирует приложение по частям. Минимальной единицей компиляции считается функция. В клиентской конфигурации, перед тем как JIT компилятор начнет работу, на сбор статистики, отводится 1500 вызовов функции. Для серверной конфигурации, где производится более агрессивная оптимизация требуется 15000 вызовов. Этого должно хватать для сбора статистики и проведения спекулятивной оптимизации кода, которая и позволяет давать высокую пропускную способность кода. На высоконагруженных системах статистика собирается мгновенно, чего нельзя сказать про десктопные программы.

В процессе оптимизации компилятор выдвигает гипотезы о функционировании кода и анализируя статистку пытается доказать или опровергнуть свои предположения. Если HotSpot опровергает свою гипотезу, то JIT компилятор производит деоптимизацию для последующей рекомпиляции уже скомпилированного кода. И проводит новые оптимизации по новым гипотезам. Чаще всего HotSpot выдвигает гипотезы о структуре алгоритма по стандартным патернам проектирования. Именно поэтому всех Java программистов учат как можно чаще использовать патерны проектирования.

Даже если вы сможете придумать оригинальный эффективный алгоритм, то это не значит, что ваша программа будет работать быстрее, скорее наоборот. HotSpot будет слишком часто выдвигать ошибочные гипотезы о вашем коде, проводить деоптимизации и рекомпиляции до тех пор пока не сможет найти оптимальный способ компиляции вашего алгоритма. И не факт что JIT оптимизация нестандартного алгоритма, будет лучше, чем оптимизация громоздкого патерна. Инженеры SUN в свое время провели огромную работу, чтобы научить Java эффективно работать с патернами.

Среди распространенных оптимизаций HotSpot может преобразовывать медленный вызов виртуальной функции, на inline подстановки. Но для этого HotSpot должен знать обо всех полиморфных преобразованиях в вашей программе. Зная где чаще всего вызывается определенная функция, компилятор перемещает её как можно ближе к тому месту, где она используется. Подобный уровень производительности может быть достигнут в Си при ручном управлении inline вызовами. Но при использовании объектно ориентированной парадигмы в С++ это затруднительно, а иногда и невозможно. Поэтому при достаточно большим сборе статистики HotSpot может приблизиться к производительности Си, и превзойти С++. И если мы используем патерны, то HotSpot их узнает и проводит нужные подстановки с первого раза. Но если у вас нестандартный алгоритм, то в этом случае гипотезы HotSpot о проведенных подстановках будет оказываться ложными с большей вероятностью. И хотя при этом будет собрана новая статистика для более эффективной оптимизации, запуск JIT компилятора происходит через строго заданные интервалы итераций.

По умолчанию, как было сказано выше, это 1500 итераций для десктопных программ. Это может показаться слишком большим значением. В этом случае поведение HotSpot можно изменить с помощью параметра -XX:CompileThreshold. Этим мы можем уменьшить время на разогрев HotSpot. Но уменьшив время на разогрев, мы не уменьшим время сбора статистики. Статистика собирается исходят из активности работы программы. Поэтому установив слишком маленькое значение -XX:CompileThreshold=1, мы только заставим JIT компилятор очень часто работать в холостую, особенно после старта, когда новая статистика поступает непрерывно. Это тоже исказит статистику производительности. При современных мощностях процессоров, оптимальным значением можно считать -XX:CompileThreshold=100. Но для интереса рекомендую поэкспериментировать с этим параметром, так как он очень чувствителен к алгоритму. Для наблюдение за работой JIT компилятора используйте в консоли параметр -XX:+PrintCompilation. Уверяю вас, это будет очень познавательное наблюдение.

Таким образом результат теста производительности зависит от того как долго HotSpot собирала статистику по алгоритму. И если статистики нет, то и JIT компиляции нет. Измеряя статистику Java, нужно четко понимать, что именно мы измеряем — производительность интерпретатора, производительность не оптимизированного кода или производительность оптимизированного кода. А то получится как в анекдоте, — “Не важно как проголосовали, важно как посчитали.”

Вместо эпилога.

Можно ли считать подобную архитектуру достоинством или недостатком? На этот вопрос каждый ответит для себя лично, и каждый будет прав. Java умеет решать все поставленные перед ней задачи, но нужно хорошо знать как она это умеет делать и иногда приходится в чем-то идти на компромиссы. Но программисты как никто другие знают, что такое радость творения. Самым сильным мотиватором для работы программиста является радость и удовлетворение от созерцания работы своего труда. Поэтому работодателям так трудно мотивировать нас деньгами. [1] Мы (по крайней мере большинство из нас) любим свою работу за то, что результат нашего труда приносит нам радость, и при этом, как приятный бонус, еще неплохо оплачивается. Поэтому мы любим и те инструменты, которые помогают нам быть более счастливыми и выстраивать творческие взаимоотношения с нашими заказчиками, коллегами и сообществом. Получайте радость от своей работы, будьте счастливы на работе и вам не придется думать о том как заставлять себя что-то делать. Человек подсознательно стремиться к положительным эмоциям и уходит от отрицательных, поэтому выстраивайте положительные творческие взаимоотношения в своей работе и ваши руки сами будут хотеть работать. Если вы не получаете радость от своей работы, значит вы делаете что-то не то что должны. Делайте то, что сделает вас по настоящему счастливыми, делайте то, что вы действительно можете, — делайте мир лучше.

Будьте счастливы. Пейте кофе. Пишите Java.

UPD: Исправил опечатки. Огромное спасибо Zeij за редактуру текста.
Tags:
Hubs:
Total votes 114: ↑97 and ↓17+80
Comments75

Articles