Pull to refresh

Comments 41

А по какой причине шейдеры в GLSL кроспилируются каждый раз в рантайме заново, а не заранее на этапе сборки и не бандлятся в игру/приложение собственно в виде GLSL?
Конечно, в случае медленного/тяжёлого транслятора они транслируются заранее. Неудобства будут в основном у разработчиков и моддеров, но даже им можно помочь кешируя результат в файловой системе и транслируя только по необходимости. Правда дистрибуция стороннего парсера это ещё отдельный вопрос.
Т.е. всё решаемо, но мне было интересно откуда же берутся эти цифры, ведь тут 3 секунды старта, там 30 мегабайт исходников, так проекты и становятся неповоротливыми.
К тому же, я тут рассматриваю абстрактный парсер, у которого применением может быть не только трансляция шейдеров, но и, к примеру, скриптование.
Тогда ещё вопрос — основной платформой и для текущего кроспилятора и для «к примеру, скриптования» является Android? Почему именно Java (как платформа в первую очередь, JVM)? Понятно, что кроспилированные шейдеры можно использовать в сыром виде, но что со скриптованием? Рассматривали ли вы какие-то альтернативные варианты типа скриптового рантайма на С например?
Конечно, выбор платформы это тема для совсем другой статьи, но лично моё мнение — Java, как и C#, позволяют разработчику быть гораздо эффективнее, чем… эм, многое другое.
1) 21-ый век на дворе, а JMH почему-то не пахнет. Как так? Набросал замер на JMH — получается, что IntelliJ парсер в 4 раза быстрее чем JavaCC.
2) GaugeKotlinIntellijParser зачем-то пересоздаёт окружение при каждом парсинге. Зачем так? Его же переиспользовать можно и нужно.
3) yk.jcommon.utils.IO#readFile(java.lang.String) не закрывает файл
Мне кажется так как претензия конкретно к холодному старту, JMH тут не имеет смысла использовать.
Если бы речь шла о скорости работы SQL запроса в enterprise приложении, то, да, её можно и без JMH измерить. А тут наоборот: JMH проще, быстрее и точнее.

1) Вообще говоря, «производительность» автор объявляет первым требованием
2) В JMH без проблем можно измерять «холодный старт». И измерять это можно более полно, т.к. в JMH есть встроенный механизм Forks — будут запускаться отдельные Java машины
3) Код на JMH гораздо компактнее, чем «рукописный». В итоге, человеку со стороны проверить гораздо проще
4) В JMH встроено много разнообразных профайлеров. Автор приводит цифры без какого-либо анализа того *почему* цифры именно такие
Про SQL — это несколько пренебрежительно, не находите?

1. Первое требование — удобство разработчика и пользователя.
2. Тут я меряю конкретно то что мне нужно.
3. Компактнее не выйдет. При сохранении функционала, конечно.
4. Вы невнимательно читали.

5. Мне не нужна цифра «время исполнения в Х итерациях после У разогревов». Мне нужно минимальное время к первому, десятому, сотому, тысячному вызову.
6. На 1000й вызов JetBrains моя цифра выходит чуть больше чем в JMH, на 50000й — меньше. Как я указывал, при таком агрессивном JIT, меня интересует именно динамика. А конкретное число — вообще имеет мало смысла. Я бы ещё понял претензию об однообразии данных!

Ну и мелочи. Мои тесты проходят за пару секунд, а не за минуту, ещё и форматируется это всё, чтобы в любой момент можно было повторить замеры и скопипастить в нужный отчёт.

Думаю, в 21м веке каждый волен использовать любой удобный инструмент, если понимает что он делает.
1. JMH интересен, конечно, и иногда цифры даже совпадают на стотысячный вызов. А иногда мой стотысячный выходит быстрее. В общем, это совсем другая тема, и для статьи, возможно, надёжнее было бы выложить именно его результаты. Но в любом случае, это всего лишь тысячный вызов, а, как я написал — меня интересовала именно динамика времени.
2. Отличное замечание! Действительно, кешируя именно это значение можно ускорить JetBrains в два с половиной раза (в данных конкретных условиях).
Kotlin IntelliJ parser (without analyzer, cached project)
first call elapsed : 1272.145ms
min time in next 10 calls: 5.031ms
min time in next 100 calls: 1.486ms
min time in next 1000 calls: 0.38ms
Whole time for 1111 calls: 1.518542s

3. Файл закроется в finalize, но вы правы, лучше делать это явно. Тем более раз он начал использоваться более чем для чтения пары файлов.
4. А вот почему в вашем замере ускорение в четыре раза — это действительно интересно. У вас используется CoreLocalFileSystem и именно он даёт буст. С первых же вызовов 0.4мс вместо 5. Уж не кешируется ли там результат парсинга?

Kotlin IntelliJ parser (without analyzer, CoreLocalFileSystem)
first call elapsed : 1233.702ms
min time in next 10 calls: 0.418ms
min time in next 100 calls: 0.127ms
min time in next 1000 calls: 0.041ms
Whole time for 1111 calls: 0.11175224s
В №1 вы говорите об изменившихся цифрах. Но вы замеряете ДРУГУЮ имплементацию, а пишите что только заменили методику сбора.
Нужно быть внимательнее в построении предложений.
Я правильно понял, что посмотреть можно только на парсер, транслятор закрыт? Что же касается обсуждаемого вопроса: мне кажется, что тащить компилятор в продакшен рантайм занятие достаточно бессмысленное, ничто не мешает скомпилировать заранее, а для разработки можно и размер увеличить и подождать немного один раз.
мы ведь тут говорим и запуске шейдеров, а не о скриптовании
Да, можно не тащить. Но и о разработчике я бы не стал забывать тоже, он ведь тот, кто чаще чем кто либо другой будет запускать проект. Ну и habr.com/post/433000/#comment_19503612
Я ведь и не предлагаю забывать. для разработки можно сделать режим разработчика, который используя тот же код может отслеживать и перекомпилировать на лету, для продакшена же это совершенно нужный инструмент, который добавляет сложности этой логике и вероятных ошибок
я это все к тому, что намного интереснее было бы посмотреть на транслятор, чем на парсер
Транслятор пока не открывал
+ потестить профайлером на предмет неоднозначностей (ambiguity) в правилах. Грамматики из antlr репозитория часто ими грешат.
Да уже: обобщенный алгоритм LL(*) (а точнее ALL) хоть и упрощает написание произвольных грамматик, однако усложняет сам код парсера, ресолвинг неоднозначностей. Поэтому нужно аккуратно их писать. К тому же он динамический.
Спасибо за ссылку, обязательно почитаю! Интересно будет узнать что в ANTLR можно сделать лучше. Однако, первой же в голову приходит мысль — а ведь в JavaCC я писал «в лоб», без каких либо ухищрений. И размер fat jar со временем первого старта — вряд ли изменятся.
(Кстати, грамматика на ANTLR — не моя)

А почему размер jar так важен? И у меня намного меньше:


  • C# Optimized: 307 Кб (бинарь Kotlin.dll) + 1.48 Мб (размер рантайма).
  • C# Standard: 305 Кб (бинарь Kotlin.dll) + 352 Кб (размер рантайма).
  • Java: 671 Кб (скомпилированные файлы .class) + 330 Кб (размер рантайма).

Проверял с помощью моей тулы DAGE (пока что нестабильная версия, поэтому возможны баги).

Я не говорю что прямо так важен, но обращать внимание на него стоит, ведь даже на десктопе — это не просто 30МБ на терабайтном винте, это 30МБ кода.
(Плюс абзац из публикации, не буду повторяться).

Размер я мерял со всеми зависимостями.
kotlinAntlr-0.1-SNAPSHOT-jar-with-dependencies — 15МБ, основной вес в зависимости com.ibm.icu
1) А зачем в зависимостях указывать <artifactId>antlr4</artifactId>, когда во время выполнения достаточно <artifactId>antlr4-runtime</artifactId>?
2) Зачем в jar-with-dependencies включать junit и hamcrest, ведь они только для тестов нужны? Зачем в измеряемый jar включать log4j и commons-lang3, которые вообще не используются в коде? Измерять нужно не jar-with-dependencies, а результат работы maven-shade-plugin'а. Либо оставляйте только реально нужные зависимости. Если всю эту ерунду удалить, то получается где-то 1 мегабайт.
3) Если удалить и yk/jcommon, то получается похоже на результат KvanTTT
Стоит упомянуть, что теперь большую часть занимает не ANTLR и не парсер, а yk.yast:common:jar и yk:jcommon:jar
Это же относительные результаты, понятно что во всех случаях что-то дополнительное есть. Но если таки заморочиться, то JavaCC выйдет ещё красивее — 66КБ против ANTLR 680КБ. О чём я, кстати, упомянул.
Ещё раз: проблема не в конкретных цифрах, а в том, что они означают совсем не то, что читатель предполагает. Вы пишете, что «парсер на ANTLR занимает столько-то», и логично предполагать, что там «парсер+необходимая ANTLR обвязка».
А по факту же, вы туда вкладываете свои commons (которые занимают половину результитрующего jar), log4j, commons-lang и далее по списку.
Я нигде не говорил что это размер чистого парсера. Написано: "… посмотреть на размер джара со всеми зависимостями".
Кроме того, я везде привожу ссылки на код, который тестируется. Как тут можно что-то не так понять?
И ещё раз. Тем не менее. Я упомянул. Про чистый размер парсеров.
Как тут можно что-то не так понять?

Ясно как: никому и в голову не придёт, что вы, помимо необходимого, в jar включаете десяток-другой абсолютно лишних зависимостей.
И ещё раз. Тем не менее. Я упомянул. Про чистый размер парсеров.
В начале статьи лишь слова «размер джара (парсера) = 1.4МБ».
Слово jcommon в статье вообще не встречается.
log4j тоже не встречается.
То, что в JetBrains парсер входит Kotlin runtime тоже нигде не говорится.
Это название колонки в таблице :) В секции tl;dr :)
На JavaCC парсера не нашлось, а вот на хайповом ANTLR, ожидаемо, есть (раз,
два).

Вообще говоря это одинаковые грамматики. Ну и ANTLR не такой уж и хайповый — там один мэинтейнер, который мержит пулл-риквесты раз в полгода. А версии выходят раз в год.

Да, там мёрж грамматики одного автора в общий репо. Подробности поленился расписывать.
JavaCC парсер пока содержит не всю грамматику

А какой процент unit-test'ов (я про официальные Kotlin тесты) проходит этот парсер?

Например, JavaCC парсер считает, что -789 относится к определению переменной x, хотя это, очевидно, не так:

fun getResult() {
val x = 123 + 456
- 789
}
Не запускал, конечно. Грамматика не покрыта же ещё.
Кстати, интересно было бы по ANTLR посмотреть. Там наткнулся что вот такой код SimpleClass().foo<String>("") не парсится.
К слову, в ANTLR грамматике какая-то самодеятельность: callExpression в официальной Kotlin грамматике отсутствует. Из-за подобного растёт неоднозначность, время парсинга и наверняка будут ошибки
осталось процентов 10. Но не похоже чтобы они могли повлиять на производительность


1) У вас не реализован парсинг functionType, а это довольно мутная часть языка (леворекурсивная и всё такое)

2) У вас не реализован парсинг functionLiteral, а это тоже непростой синтаксис. Например, в конструкции class Child: Parent by delegateMethod { ... } фигурные скобки могут быть как телом класса Child, так и лямбдой-параметром к методу delegateMethod

В общем, предлагаю сначала пройти хотя бы те тесты, которые проходит ANTLR парсер, а потом уже делать какие-либо сравнения.
Sign up to leave a comment.

Articles