All streams
Search
Write a publication
Pull to refresh
24
0
Send message

Поэтому даже шаблонный транслятор уже ускоряет код. Но ведь это все jit-пропаганда и мы все врем, а если результаты time говорят о другом, то тем хуже для time. Теория выше экспериментов!

Ассемблер приведён для ознакомления. Где в статье есть выводы на основе ассемблера, кроме количества байт-код инструкций? Кстати Вы можете определить реальное количество тактов процессора только по asm-коду? Отлично! А сколько будет инструкций микрокода? Вы же знаете, что в разных x86 процессорах микрокод разный и микро архитектура разная. Знаете? А что такое микрокод x86 знаете, ведь знаете? Ну хотя бы свои данные от запуска с time приведете?

Да и самая суть не в этом. Ну будет ваш интерпретатор на каких-то версиях x86- процессоров чуть быстрее, все равно сравниваются разные контексты. ГДЕ ОБЕЩАННЫЙ ОБГОН JIT/AOT я спрашиваю?!

Так пишите на чем хотите. Какое Вам дело до моих личных предрассудков :)? Ну плачут разработчики оптимизирующих бэкендов по ночам. Но это же никому не видно и не слышно :)

Вы не поверите, но Java/CoreNET я не люблю. А что касается JS/Python/Lua и других языков с динамической типизацией, то я их ненавижу. ЛЮТОЙ НЕНАВИСТЬЮ. Такой, какой может быть только у разработчика оптимизирующего бэкенда компилятора. Но если их используют, то что делать?

А да, забыл добавить, а еще я НЕНАВИЖУ C++, если только это не Си с классами. А комитет по стандартам C++ канделябрами, канделябрами. Что б не повадно было.

$ time java Main > /dev/null
real 0m1,786s

$ time java -Xint Main > /dev/null
real 0m6,686s

model name : Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz
openjdk 11.0.6 2020-01-14

Кстати, 6.7 s на openjdk - это меньше, чем 7.7 s на asmopt.

В репозитории из статьи взял ветку asmexp:

$ time ./asmopt > /dev/null
real 0m7,739s

$ time ./asmexp > /dev/null
real 0m8,787s

$ time ./native > /dev/null
real 0m1,253s

В статье приведен другой репозиторий. В чем отличие? Чем asmoptll.S отличается от asmexpll.S? Что должны демонстрировать тесты native, asmopt и asmexp?

Как рассказывали знающие люди, еще в самом начале пути, разработчики java-машины были уверены, что им достаточно сделать только интерпретатор. Это сейчас говорим java-машина, а подразумеваем jit-компилятор. Но так было не всегда. Однако, очень быстро разработчики убедились, что jit-компилятор делать придется. И да, производительности много не бывает. Поэтому разработчики java-машины (лет 40 назад?) уже наступили на эти грабли. И нет, сколь угодно быстрого интерпретатора недостаточно.

Можно сказать, ну всегда найдутся ниши там где производительность не важна. Ну во-первых что это за ниши такие и кому это будет интересно? А во-вторых, а зачем тогда быстрый интерпретатор, производительность-то не важна? Да и дело в экосистеме. Кого, как, куда и кто пересадит на совершенно новую ВМ? И Зачем?

Не являюсь разработчиком JVM, хотя действительно в свое время не мало просидел в отладчике gdb и над кодом генерации кода интерпретатора, и над уже сгенерированном в памяти кодом самого интерпретатора. Не меньше, если не больше времени просидел в отладчике gdb над кодом генератора Baseline транслятора в Firefox. Медитировал над исходными кодами. Что было, то было :). Разработал даже как-то интерпретатор одного регистрового байткода целиком на ассемблере для одного процессора со статическим планированием :)). С производительностью близкой уже к шаблонному транслятору. И это тоже было.

Теперь, что касается данной статьи и моих начальных "оценок". Тут я с "оценками", мдя, но не буду о грустном :). Все что автор рассказал про свой интерпретатор, все классно. Технологии им примененные и достигнутый результат (для одной конкретной стековой машины) достойны серьезных похвал. Кстати, как и достижения разработчиков JVM. И разработчиков V8.

Но вот когда в конце статьи целиком и полностью посвященной одному конкретному интерпретатору, пусть и наиболее быстрому, делается вывод о JIT-компиляции. Хм. А где в статье разбор jit-компиляции-то, чтобы выводы о ней делать? Поскольку интерпретатор наиболее быстрый, то jit-компиляция ... А что собственно jit-компиляция-то?

Не будь таких выводов о jit/aot - и это была статья с совсем другим посылом.

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

Что касается сравнений. Если была бы речь про то, что ваша реализация, например, java-машины как системы целиком быстрее, то тогда понятно. Берем одинаковые Java программы и сравниваем общее время для двух машин. Но при этом гарантируем для программистов идентичную семантику java-программ. Но все равно это про системы целиком. И тогда могут сравниваться подходы в целом.

В общем, подобное надо сравнивать с подобным. Если сравниваются два интерпретатора, то у них должны быть одинаковые байт-код программы на входе. Ну и по-хорошему идентичная семантика. Иначе это сравнение в непонятных попугаях. Тогда некорректно говорить, что один быстрее другого. У них контекст разный. Именно как интерпретаторов а не VM.

При этом в целом возможно ваши подходы в чем-то и эффективней подходов из openjdk. А может они сопоставимы по эффективности. А может у OpenJDK чуть эффективней.

Однако в любом случаи, когда вы, Михаил, в конце статьи переходите к обобщающему выводу относительно jit-компиляторов доводов не хватает. Собственно вот что "зацепило" именно меня :). И это безотносительно ваших реальных достижений по созданию очень быстрого интерпретатора для конкретного стекового представления.

Михаил, конечно большинство моих комментариев были идиотскими (впрочем может как и этот), но и они отчасти есть следствие очень неаккуратных формулировок

> Является ли JIT (или AOT) - нашей последней надеждой на производительность? Думаю, я смог ответить на этот вопрос.

> Вывод: Java-код не в состоянии обогнать оптимизированный интерпретатор байткода.

В x86-процессоре есть предсказатель переходов. Но если код интерпретатора фиксирован, и исполняется не только маленький цикл интерпретируемой программы, то и такой планировщик не панацея.

Шаблонный транслятор tier-1 раскручивает байткод в прямые последовательности в пределах одного линейного блока. Это идеальная ситуация для аппаратного планировщика. И даже на переходах из байткода ситуация на порядок станет лучше.

Кроме того, jit-компиляторы уровня tier-2 в jit-языках, на мой взгляд, чаще занимаются не столько архитектурными оптимизациями, сколько оптимизациями этих самых языков. (Особенно это наглядно для языков с динамической типизацией.) Но и архитектурными оптимизациями тоже. Так tier-2 компиляторы преобразуют стековый байткод в регистровое промежуточное представление (в пределах линейного блока). Ну про инлайн и не вспоминаем (его и на байткоде делать можно). А нативный код, полученный из регистрового представления лучше планируется и быстрее исполняется из-за аппаратных особенностей.

Полностью с Вами согласен. Нужно любые утверждения из всех научных статей принимать сугубо на веру. В заголовке же написано про наиболее быстрый интерпретатор среди всех возможных, значит так оно и есть! Вопросы - да зачем? А всех этих дармоедов из OpenJDK, CoreNET и V8/Chrome надо разогнать к чертям собачьим.

Элементарно есть масштабируемость по количеству итераций цикла? Когда внутренний цикл погружаем во внешний, чтобы исключить постоянные накладные расходы на раскрутку.

При старте java-машины происходит инициализация стандартной java-библиотеки. Java-библиотека написана на java ("ДБ." Лавров.) И это дорогой процесс. А только потом "пролетает" код собственно примера.

ДОПОЛНЕНИЕ. Хотя не-а, похоже я сильно ошибся, насчет сложности старта java-машины. Либо в примере должны быть import'ы или new Object.

Ок, было сказано, что

> Java-код не в состоянии обогнать оптимизированный интерпретатор байткода

А где сказано, что количество байт-код инструкций при обоих сравнениях одинаковое, и где обоснования, что семантика байткод инструкций идентичная?

Как без этого можно утверждать, что один интерпретатор быстрее, и Вы всерьез полагаете, что в OpenJDK не оптимизированный интерпретатор?

Просто абсолютное общее время работы не говорит само по себе ни о чем.

Если исходить из научного дискурса, поиска знаний.

То было у меня такое исследование - сделать интерпретатор регистрового представления. В компилятор javac (из OpenJDK) сделал патч, в рамках которого из Java-AST вместо стандартного байткода стандартной стековой машины создавался регистровый байткод самопальной виртуальной машины. Утверждать о корректности сравнения конечно не могу, кроме просто интерпретации кода есть еще семантика стандарта исходного языка. Но в первом приближении, можно предполагать, что удалось достичь паритета между стековым интерпретатором из OpenJDK и регистровым интерпретатором из самопальной ВМ. При этом интерпретация одной регистровой инструкции конечно занимает больше времени, но в среднем регистровых инструкций меньше. Вот и вышел баш на баш.

При написании регистрового интерпретатора пришлось долго "воевать" с компилятором gcc, чтобы заработали jump'ы из asm-вставок и ручное распределение части указателей на регистры (gcc все норовил сделать ряд оптимизаций).

Поэтому с научной точки зрения, опираясь на очень косвенные данные, можно осторожно предположить, что для x86, то есть out-of-order процессора (arm/mips сюда же видимо), производительности стекового и регистрового интерпретаторов в общем и целом равны, если каждый из интерпретаторов достаточно оптимизирован. Но это как отправная точка для исследования.

В этом смысле, разработчики OpenJDK/Firefox/CoreNET/Chrome вложили очень много сил и времени в разработку быстрых интерпретаторов стековых машин. И с наскоку их обойти, ну наверное, не то чтобы не получится, но затруднительно. Хотя может и получится. Но нужны корректные сравнения и легко читаемые "проценты".

И хотя Baseline в Firefox - это по сути шаблонный транслятор, включающий при этом паттерны динамической детипизации (JS - ужасный язык), но в плане работы со стековым байткодом, Baseline как брат-близнец похож на генератор интерпретатора в OpenJDK. Предполагаю, что и в CoreNET и V8/Chrome принципы быстрой интерпретации стекового представления схожи. Коммуниздят идеи друг у друга и не краснеют :).

Кроме того мой комментарий был про то, что прежде чем в очередной раз изобретать велосипед, то есть придумывать самыйбыстрый интерпретатор байт-кода стекового представления, прежде хорошо изучите устройство и принципы работы таких интерпретаторов, как интерпретатора байт-кода в OpenJDK.

Хотя должен также предупредить, что в виде C++ или даже asm-кода его в исходных кодах проекта Вы не найдете :), там есть только C++-код генерации кода интерпретатора :))). Но когда это останавливало нас, профеcсионалов :))?

Увеличил счетчик в цикле в 2 раза (i < 2*100000)

$ time java Main > /dev/null

real 0m6,139s


$ time java -Xint Main > /dev/null

real 0m22,493s


Нативный код из-под java-компилятора быстрее java-интерпретатора. Вы ожидали иного результата?

Запуск java-машины очень дорогой процесс. Сравните время "gcc --version" и "java --version". При старте java-машины выполняется инициализация контекста. Как-то делал замер, и у меня получилось что-то вроде ~20 млн. байт-код инструкций - это только инициализация. Обработка "--version" потребует все несколько десятков инструкций. Причем много функций исполняется 1-2 раза. Однако jit-компилятор пытается многие из них оптимизировать. В итоге время компиляции не окупается, так как нативная версия функции может вообще не исполнится ни разу. Поэтому действительно "java --version" работает медленнее, чем "java --version -Xint". Но это классическая проблема любой jit-системы, выигрыш от исполнения оптимизированной версии должен перевесить время, затраченное на компиляцию. А это бывает не всегда. Для любой jit-системы можно найти анти-патерн.

Вывод - учите матчасть.

Information

Rating
Does not participate
Registered
Activity