Как уже знают все Android-разработчики, Google недавно объявила об официальной поддержке Kotlin в Android. Многие риски, связанные с использованием этого замечательного языка в Android-проектах, сняты. Но актуальным, особенно для очень крупных проектов, каким является Badoo, остаётся вопрос о скорости сборки. Я был рад обнаружить, что в сети уже есть исследования на эту тему, и переводом одного из них хочу поделиться.
Итак, если вы переводите приложение с Java на Kotlin, будет ли оно компилироваться дольше?
В более ранней статье обсуждалось конвертирование Android-приложения из Java целиком в Kotlin. Кода на Kotlin получалось меньше, и он был удобнее в сопровождении, чем на Java, так что я пришёл к выводу, что оно того стоило. Но некоторые разработчики не хотят пробовать Kotlin, опасаясь, что он может компилироваться медленнее Java. И это беспокойство – справедливо: никто не хочет тратить время на конвертирование кода, если в результате сборка будет длиться дольше. Так что давайте изучим длительность компиляции приложения App Lock до и после конвертирования в Kotlin. Я не буду сравнивать скорость Kotlin и Java построчно, а вместо этого попытаюсь ответить на вопрос, повлияет ли конвертирование всей кодовой базы из одного языка в другой на общую продолжительность сборки.
Как я тестировал длительность сборки
Я написал shell-скрипты для повторяемых запусков Gradle-сборок по разным сценариям. Все тесты выполнялись последовательно по десять раз. Перед каждым новым сценарием проект очищался. Для сценариев, использующих демона Gradle, последний останавливался перед запуском бенчмарка.
Все бенчмарки выполнялись на машине с Intel Core i7–6700, работающим с частотой 3,4 ГГц, оснащённой 32 Гб памяти DDR4, а также SSD-приводом Samsung 850 Pro. Исходный код собирался с помощью Gradle 2.14.1.
Тесты
Я хотел прогнать бенчмарки для нескольких распространённых сценариев использования: чистые сборки с/ без демона Gradle, инкрементальные сборки без изменения файлов, инкрементальные сборки с изменённым файлом.
Кодовая база App Lock на Java содержала 5491 метод и 12 371 строку кода. После конвертирования в Kotlin количество методов уменьшилось до 4987, а количество строк – до 8564. В процессе преобразования в архитектуру не вносились никакие серьёзные изменения, так что измерение длительности компиляции до и после конвертирования должно дать чёткое представление о разнице в продолжительности сборки между Java и Kotlin.
Чистые сборки без демона Gradle
Это наихудший сценарий с точки зрения продолжительности сборки для обоих языков: запуск чистой сборки с холодным стартом. Для этого теста я отключил демон Gradle.
Вот сколько времени заняли все десять сборок:
Десять последовательных чистых сборок без демона Gradle
Средняя продолжительность сборки Java составляет [цифры исправлены по исходным данным автора — прим. переводчика] 24,5 секунд, Kotlin – 32,4 секунд: увеличение на 32%. Не лучшее начало для Kotlin, но большинство людей компилируют свой код по другим сценариям.
Чаще всего мы несколько раз компилируем одну и ту же кодовую базу по мере внесения в неё изменений. Именно для этого сценария был разработан демон Gradle, так что давайте включим его и посмотрим, что получится.
Чистые сборки с включённым демоном Gradle
Одной из проблем JIT-компиляторов вроде JVM является то, что они тратят время на компиляцию исполняемого в них кода, так что по мере его исполнения производительность процесса увеличивается. Но если остановить JVM-процесс, то прирост производительности теряется. При каждой сборке Java-кода обычно приходится запускать и останавливать JVM. В результате он каждый раз заново делает одну и ту же работу. Для решения этой проблемы Gradle поставляется с демоном, который продолжает функционировать между сборками и помогает поддерживать прирост производительности, обеспечиваемый JIT-компиляцией. Включить демон можно с помощью Gradle-команды --daemon
, вводимой в командной строке, или с помощью добавления org.gradle.daemon=true
в файл gradle.properties
.
Вот результат прогона той же серии сборок, но с включённым демоном Gradle:
Десять последовательных сборок с включённым демоном Gradle
Как видите, первый прогон занимает примерно столько же времени, сколько в сценарии без демона. В последующих сборках производительность растёт вплоть до четвёртого прогона. При таком сценарии более целесообразно оценивать среднюю продолжительность сборки после третьего прогона, когда демон уже прогрелся. В этом случае чистая сборка на Java занимает в среднем 14,1 секунды, а на Kotlin – 16,5 секунд: увеличение на 13%.
Kotlin догоняет Java, но всё ещё отстаёт. Тем не менее вне зависимости от используемого языка демон Gradle уменьшает длительность сборок более чем на 40%. Если вы его ещё не используете, то самое время начать.
Итак, полные сборки на Kotlin выполняются чуть медленнее, чем на Java. Но обычно мы компилируем после внесения изменений всего лишь в несколько строк кода, так что инкрементальные сборки должны демонстрировать другую производительность. Давайте узнаем, сможет ли Kotlin догнать Java там, где это важно.
Инкрементальные сборки
Использование инкрементальной компиляции является одним из важнейших свойств компилятора по повышению производительности. При обычной сборке перекомпилируются все исходные файлы проекта, а при инкрементальной – отслеживается, какие файлы изменились с момента предыдущей сборки, и в результате перекомпилируются только эти файлы и те, что от них зависят. Это может оказывать очень сильное влияние на длительность компиляции, особенно в больших проектах.
Инкрементальные сборки появились в Kotlin 1.0.2, их можно включить, добавив kotlin.incremental=true
в файл gradle.properties
, или через командную строку.
Итак, как изменится длительность компиляции Kotlin по сравнению с Java при использовании инкрементальной компиляции?
Вот результаты бенчмарка при условии отсутствия изменений в файлах:
Десять последовательных инкрементальных сборок без изменения файлов
Теперь протестируем инкрементальную компиляцию при условии изменения одного исходного файла. Для этого я перед каждой сборкой изменял Java-файл и его Kotlin-эквивалент. В данном бенчмарке это файл, относящийся к пользовательскому интерфейсу, от него не зависят другие файлы:
Десять последовательных инкрементальных сборок с одним отдельным изменённым файлом
Наконец, давайте посмотрим на результаты инкрементальной компиляции с одним изменённым исходным файлом, который импортируется во многие другие файлы проекта:
Десять последовательных инкрементальных сборок при условии изменения одного ключевого файла
Как видите, демону Gradle всё ещё приходится прогревать в течение двух–трёх прогонов, но после этого оба языка становятся очень близки по производительности. При отсутствии изменений в файлах у Java уходит 4,6 секунды на прогретую сборку, а у Kotlin – 4,5 секунды. Если мы меняем файл, но он не используется другими файлами, то Java требуется 7 секунд на выполнение прогретой сборки, а Kotlin – 6,1 секунды. Наконец, если изменённый файл импортируется во многие другие файлы проекта, то при прогретом демоне Gradle инкрементальная сборка Java занимает 7,1 секунды, а у Kotlin уходит в среднем 6 секунд.
Заключение
Мы измерили производительность при нескольких разных сценариях, чтобы узнать, сможет ли Kotlin конкурировать с Java по длительности компиляции. При чистых сборках, которые выполняются сравнительно редко, Java превосходит Kotlin на 10–15%. Но чаще всего разработчики выполняют частичные сборки, при которых большой выигрыш во времени достигается за счёт инкрементального компилирования. Благодаря работающему демону Gradle и включённой инкрементальной компиляции Kotlin не уступает, или даже немного превосходит Java. Впечатляющий результат, которого я не ожидал. Выражаю команде разработчиков Kotlin своё почтение за создание языка, который не только обладает прекрасными возможностями, но и компилируется так быстро.
Если вы пока не попробовали Kotlin из опасений увеличения длительности компиляции, то можете больше не беспокоиться: он компилируется так же быстро, как Java.
Сырые данные, собранные мной при прогоне бенчмарков, лежат здесь.
Комментарии (30)
PaulWeb
19.05.2017 04:22-2А да еще буквально полгода назад только начинал осваивать AR (ARToolkit, OpenCV, Google mobile vision), а уже https://awe.media/ или вот еще.
stepango
19.05.2017 09:22+4Не хватает сравнения скорости при компиляции с оспользованием кодогенерации apt/kapt, так же при таргетах java6/8(dasugar, retrolambda) а так же multidex. По сути статья в таком виде не имеет ничего общего с реальными крупными проектами, где скорость компиляции действительно важна.
genbo
19.05.2017 09:25+4Средняя продолжительность сборки Java составляет 15,5 секунд, Kotlin – 18,5 секунд
При этом на графике для Java почти все результаты попадают в 20-25 сек (и выше), а Kotlin — более 30 сек.ArkadyGamza
19.05.2017 12:21Действительно. К счастью автор выложил исходные данные, поправил цифры в переводе.
stepango
19.05.2017 12:35+3Какая версия kotlinc использовалась при компиляции? Почему для тестов был выбран Gradle 2.14 а не 3.5?
Beholder
19.05.2017 14:15Ну да, упомянули 1.0.2, которая уже даже не вчерашний день, а позавчерашний.
К тому же сомнительно, что код, полученный автоматической конвертацией, является оптимальным. И ещё мне кажется, что Gradle под Kotlin ещё пока не так хорошо заточен, как Maven.nerumb
19.05.2017 14:26+1И ещё мне кажется, что Gradle под Kotlin ещё пока не так хорошо заточен, как Maven.
Там обратная ситуация. В Gradle и скрипты можно теперь писать на Kotlin и в нем раньше появилась инкрементальная компиляция для него.
Да и сама система сборки создавалась не жестко под один определенный язык (по сравнению с Maven), и как результат, имеет более гибкую систему настройки.sshikov
19.05.2017 21:06В каком месте у мавен жестко определеный язык?
nerumb
19.05.2017 21:11… сама система сборки создавалась не жестко под один определенный язык
sshikov
19.05.2017 21:18+1Вот я и спрашиваю, где вы это вычитали? Мало того, что уже много лет существуют скажем плагины для сборки .Net проектов (потому что эта сборка в общем-то ничем не отличается от сборки java проектов), или скажем Flexmojo, мало того, что сами плагины пишутся на Groovy, кложе, и еще на некоторых разных языках.
В мавене нет никакой привязки к языку, кроме той, что он сам на java написан. Мавен — это лишь формат POM (который в целом language agnostic, да еще и сам давно уже может быть переписан например в виде yaml). Ну и соглашения некоторые, типа жизненного цикла сборки или структуры папок — которые тоже ни к какому языку не привязаны отродясь.
nerumb
19.05.2017 21:56+1Использование Apache Maven – обратная сторона медали
Ну и соглашения некоторые, типа жизненного цикла сборки или структуры папок…
Поэтому и существуют некоторые сложности для сборки других языков.
П.с. пока искал нашел еще одну интересную статью: Maven is broken by designsshikov
19.05.2017 22:15Сложности? Не большие, чем у любого другого средства сборки. Попробуйте для примера, использовать какой-нибудь современный менеджер зависимостей, типа bower, на машине в интранете, с доступом вовне через прокси, которая генерирует на лету SSL сертификаты. Проникнитесь с ходу.
А соглашения кстати соблюдать не обязательно. Я писал плагины, работающие вообще без наличия POM, это вполне возможно. Пишете на груви, внутри делаете вообще что хотите.
Статья кстати дурацкая. Не, не вся, разумные мысли там есть — только вот решений проблем автор все равно не предложил. А местами просто ржака:
The POM as used in repositories is too verbose for its intended use, and could be vastly improved. Slimming it down would be possible, but enhancing it by making it no longer immutable would break everyone. Unfixable.
Ха (три раза). POM в репозитории — это единственная вменяемая форма модели, с которой вообще можно работать. Я бы сказал, что все другие модели значительно хуже, но опыт применения скажем npm маловать для столь категоричного высказывания.
sshikov
19.05.2017 22:20Чтоб было понятнее — автор этой статьи считает, что возможность построить модель проекта динамически, используя для этого groovy — это хорошо. На мой же взгляд — это ужасно.
Чтобы так делать, нужно чтобы у вас был gradle в наличии. Причем желательно — той же версии, с теми же плагинами. И надо сборку фактически выполнить, чтобы модель получить. Мало того, что это делает импорт скажем gradle проектов в IDEA просто на порядки медленнее — это еще и не позволяет работать с проектами из других языков. При этом с pom я успешно работал любым инструментом — потому что это xml.
Ну и кто тут привязан к одному языку?
Понятно, что у него другие use cases, но совершенно очевидно, что он обобщает без оснований.
amarao
19.05.2017 15:49-2Ключевой проблемой для kotlin является java, а точнее, юридический отдел oracle. Процесс появления джавы на сервере или на слейве для сборки окутан юридическим маразмом для роботов на 200% процентов. Специальный хук в pbuilder для того, чтобы принимать лицензию до установки пакета.
Оно отвратительно. Говорю как админ.SerCe
19.05.2017 16:50А зачем ставить oracle-java? Почему не openjdk? Почему не zulu?
amarao
19.05.2017 16:59Программисты говорят, что неоракловская джава им по религии и внутреннему мироустройству не подходит. Почему в реальности — не знаю. Я админ, а не явописатель.
crea7or
19.05.2017 21:11-2оракл переводит отдел в котором разрабатывали jvm, в том числе, из Питера в Бангалор. Так что может и стоит взглянуть на альтернативы.
PaulWeb
Не знаю переводил кто данную статью (даже не искал пока), просто все равно не понятно для чего переходить на Kotlin, тем более samsung выпустил Tizen OS, google готовит fuchsia c dart плюс progressive web app, microsoft подсаживает на typescript, это я к чему, хотя и был плохой опыт с phonegap, и не понятно кто выживет из всего этого зоопарка, мобильная разработка движется к кроссплатформенности через ecmascript, ну или Dart. У Вас кстати есть хороший пример по работе с сенсорами используя rxjava было бы интересно посмотреть как это бы выглядело на Kotlin.
semmaxim
Kotlin умеет в JavaScript.
PaulWeb
Посмотрел точно, спасибо за подсказку, еще приведу цитату с официального блога
если коротко есть Kotlin/JVM и Kotlin/JS, а в планах и macOS, iOS и IoT/embedded systems.
Тогда смысл есть на перспективу, если начинать новый проект, лишь бы его не шатало как python, swift и т.д.
nikitasius
Как бы и Java умеет в JS тоже и весьма неплохо. В бытность одного из моих сайтов мне надо былло дешифровывать ссылки, которые были на другом ресурсе закодированы в aes256 (в JS был код от А до Я). Декодирование на оригинале было на aes256 с пост обработкой (перестановки символов и т.д.). Чтобы не ломать бошку, я просто зарядил код в
ScriptEngineManager
, чтобы не тратить время на реализацию на самой java.PaulWeb
Ну как бы на java можно и php скрипты запустить, а вот проект с Java на JavaScript
Optik
Речь о транспилере с языка на язык.
nullc0de
Так есть еще GWT от гугл, который транспайлится в js, на замену которому пришел dart… Под dart есть angular 2, fuchsia… А что есть под Котлин?
nullc0de
Dart как бы это уже давно умеет из коробки, и даже намного лучше…