Pull to refresh

Comments 8

Сдаётся мне, всё-таки есть проблемы с методикой тестирования. По всем тестам, которые я видел, у Ruby и JRuby по разным тестам примерно сопоставимые результаты. Но так, чтобы JRuby был в 10 раз медленее — это что-то совсем новое. Что там внутри WEBrick — понятия не имею. Может, что-то дёргается из того, что плохо реализовано в JRuby. Может, на каждый чих поднимается новый процесс c JRuby (как известно, для JVM это тяжёлое испытание).
Нет, не должен новый процесс подниматься, это не unicorn и не passenger.
Предложите свой вариант тестирования. Мне лично совсем не хотелось сравнивать груши с яблоками (Thin vs Glassfish).
К сожалению, боюсь, бенчмарк не достаточно real-world. Мы с вами говорим о реализации языка. Соответственно, измерять надо в первую очередь его. Здесь же измеряется networking, io, но никак не сам рантайм.

Во-первых, приложение должно быть побольше — здесь, как мне кажется, не хватает логики.

А во-вторых, у меня есть подозрение, что вы совершенно не учитываете особенности JVM — виртуальной машины, которая из коробки хорошо приспособлена для Java, а никак не для Ruby. Ей нужно время на старт, на «прогрев», т.е. на то, чтобы JIT не только включился, но и отработал все «теплые» участки кода (они заJITятся), а также выделил среди них действительно «горячие» — их JVM вначале декомпилирует, а затем JITит с большим числом оптимизаций (именно за счет таких горячих участков Java-приложения по скорости сопоставимы с native кодом). Кроме JIT есть еще и GC. Сборщиков мусора для разных поколений объектов в Java несколько, все они параметризируемы, и все они требуют времени, чтобы войти в оптимальный режим работы. В интернете полно материалов относительно того, как надо «готовить» Java для бенчмаркинга. И мне кажется, что вы проигнорировали их все.

Если вкратце, то на бенчмаркинг нужно отвести час. Замеры начинать только после двадцатой минуты (тут всякие варианты могут быть). Если результаты оказались ниже ожидаемых, следует мониторить GC и менять его параметры соответственно. Это может показаться чрезмерно сложным. Но по факту могу сказать: большинство Java-приложений на продакшене проходят подобный тюнинг. И в общем случае обкатка системы может занять несколько дней. Но поверьте, оно того стоит: путем такой настройки добиваются роста производительности в разы.

Люди решают использовать JRuby, потому что знают о том, какой прирост они могут получить от JVM. Если вы с JVM так близко не знакомы, то у вас есть выбор:

1. Либо вы учитесь работать с ней — и тогда JVM станет вашим секретным оружием.
2. Либо продолжайте пользоваться YARV. Вам не придется думать обо всех этих страшных вещах вроде GC-тюнинга. Вместо этого можете сфокусироваться на той бизнес-задаче, которую вы хотите решить с помощью Ruby.

Только пожалуйста, если вы относитесь ко второму лагерю, не надо делать ничего не значащие бенчмарки, способные ввести людей в заблуждение.
Начну с того, что напомню одну интересную фразу — «Ruby is fast enough», которая намекает на то, что Ruby — язык медленный, но в тех условиях, в которых он применяется, а именно в сетевой среде с большим количеством именно ввода-вывода, его применение обоснованно, так как накладные расходы не критичны относительно тех задержек, которые привносят системы, с которыми приходится общаться (БД, файлы).

В реальном мире мы не просим Ruby делать Map-Reduce, мы просим его составть запрос, и просим получить результат, и составить представление ответа. Язык почитаем за простоту и быстроту разработки на нём.

Я последние 8 лет проработал именно с Java, и знаю его особенности, поэтому данные теста это не первый прогон, это третий (спустя несколько минут после старта сервера, и спустя уже 500 запросов). Мониторил и использование памяти. Вспоминая, как у меня tomcat стартовал 5 минут с 500MB'айтным WAR'ником и моментально подвергался дикому напору JMeter'а, при этом не деградируя и не улучшая своей производительности сутками. Дело не в этом.

Ещё одна проблема, которую я в данном тесте не затрагивал — это больше потребление памяти в случае с JRuby/JVM, нежели с YARV. Да, можно настраивать GC на параллельность и прочее, и это может дать прирост. А может и не дать.

Не могу найти интересного теста, где сравнивалась Spring и Rails, а как мы знаем Java в 20 раз быстрее на синтетических тестах, и Rails оказались на 50% быстрее. А Rails — далеко не самый быстрый фреймворк, он в 5 раз отстаёт от голого Sinatra.

Это и есть real-world, а не сферические кони в invokedynamic вакууме.

Тест проводился для того и другого «из коробки». Комментарий по поводу тюнинга JVM опциями см ниже.
Хм, радует, что действительно время потратили — просто из первоначального поста я этого не увидел. Про большее потребление памяти JVM по сравнению с YAVR мне отлично известно: это проблема большинства языков на JVM. Все-таки виртуальная машина, созданная для того, чтобы эффективно исполнять байткод статического языка двадцатилетней давности, не очень-то подходит для динамического Ruby. JRuby внутри очень сложен. Полиморфные кэши методов и синтетические классы съедают дофига ресурсов. Плюс несколько компиляторов (насколько я помню, есть у них базовый интерпретатор и свой JIT). У того же Jython или Groovy та же проблема. И та же проблема у IronPython и IronRuby на CLR. Даже Rubinius на LLVM ест больше памяти.

Вообще перед любым разработчиком языка возникает эта дилемма: либо использовать существующую VM, либо писать свою с нуля. В первом случае получаешь готовые библиотеки для IO, Юникода, DateTime и т.п. Плюс не надо беспокоиться о JIT или GC. Но с другой стороны, все, что ты получаешь бесплатно, тебе не совсем подходит. Как вещи старшего брата: тут жмет, а тут наоборот слишком свободно.

Своя VM — дело трудоемкое, требует времени, но результат может оказаться лучше. Примеров масса:

— виртуальная машина для Java на базе LLVM+VMkit работает в разы медленнее, чем HotSpot;
— PyPy обгоняет и Jython, и IronPython;
— V8 и IonMonkey быстрее, чем Rhino;

Обратное тоже бывает: IronPython быстрее, чем CPython, например.

На мой взгляд, самое лучшее, что может быть с языком, — это несколько конкурирующих реализаций. Посмотрите на гонку v8, Caracan, SpiderMonkey — JavaScript за короткий срок переместился в топ самых быстрых языков программирования. И у Ruby ситуация похожа: YARV и JRuby в попытках обогнать друг друга ставят рекорды производительности, а MagLev и Rubinius подпирают их снизу. Каких-то пару лет назад Ruby безнадежно отставал от Питона, а сегодня они почти одинаковы.

Насчет вашего бенчмарка пара предложений:

1. Может, добавить логики в контроллер, а то простой Hello World мало что дает? Предлагаю что-то считать, поскреплять строки в цикле, поработать со списками. Было бы шикарно, если б еще и в базу данных заглядывал.

2. Попробуйте другой сервер. Я сомневаюсь, что те, кто ставит JRuby, пользуется «кирпичом». Либо Glassfish, либо Thin, либо еще что-то.

Вообще интересно, что получится.
Да вообще в целом ситуация ободряющая. JVM отличная VM, и я более, чем уверен, что мозговитые русские парни, разрабатывающие её, смогут заоптимизировать её на все случаи жизни, в том числе и на исполнение вот таких вот тупорыловатых тестов, как мой. Было бы лишнее время, можно было бы попрофилировать и понять, что к чему. Естественно, что JVM не разрабатывалась под динамику, свойственную Ruby, но есть и BCEL и т.д. В любом случае на Java писать проще, чем на C++, и со временем JRuby обойдёт YARV на голову. Rubinius хорош, но из за LLVM ему много не светит. MagLev неторопливые маньяки. Их направленность на абстракцию VM на нескольких физических была верна (и подкреплена временем).
Я верю в JRuby. Над JVM работали люди, которые в ближайшее время не будут работать над YARV. Не самоучки, а профессионалы, прочитавшие множество книг по теме. С пониманием алгоритмов. Лучше только OCaml и, возможно, Haskell.
А лучший язык — Ruby. И сейчас уже всё хорошо, и с каждым годом всё лучше.
что-то я не вижу опции -Xinvokedynamic.constants=true в вашем тесте.
читайте заметку внимательно — ускорилось в 3 раза с данной опцией.
Давайте попробуем!

Первая настройка для invokedynamic:
jruby -Xcompile.invokedynamic=false -v config.ru


real 0m33.975s
user 0m0.940s
sys 0m31.465s

Действительно, вдвое лучше, чем было.

jruby -J-XX:CompileCommand=dontinline,org.jruby.runtime.invokedynamic.InvokeDynamicSupport::invocationFallback config.ru


Сыплет ошибками по поводу не найденного Rack::Logger'а, добавления и настройки логгера привели к ещё более интересным традиционным для Java стектрейсам, «не найдена константа __file__». Оставим это, ибо разница тут не в разы, а лишь на 10%.

Используем модерновый GC:
jruby -J-XX:+UseConcMarkSweepGC -J-XX:-UseParNewGC -Xcompile.invokedynamic=false -v config.ru


real 0m33.078s
user 0m0.930s
sys 0m31.248

Обновил пост.
Sign up to leave a comment.

Articles