Каковы примеры типичных рабочих нагрузок, в которых МРТ превосходит JRuby? - PullRequest
0 голосов
/ 05 мая 2018

У меня есть веб-сервис Ruby, который я недавно проверил, улучшит ли использование JRuby (9.1.17.0, OpenJDK 1.8) производительность по сравнению с текущим использованием MRI (2.5.0). Я ожидал, что это может иметь место, потому что узким местом производительности является большой объем «базовой арифметики», который выполняется для вычисления данных ответа, и JRuby имеет тенденцию превосходить MRI в тестах с высокой вычислительной нагрузкой.

Однако, оказывается, что это не так: я пробовал много комбинаций параметров JRuby / JVM, но «устойчивое состояние» в 2 раза медленнее, чем MRI. Устойчивое состояние достигается после повторения запроса ~ 100 раз, когда JVM явно выполняет свою магию JIT, так как производительность улучшается в 2,5 раза по сравнению с первоначальным запросом.

Я хотел бы понять, является ли это ожидаемым или неожиданным поведением. Поэтому мне интересно: каковы типичные рабочие нагрузки, при которых JRuby может быть медленнее, чем MRI? И есть ли среди них «базовая арифметика на поплавках»?

(Узкое место в производительности находится в том же месте в MRI и JRuby, определенном с использованием соответствующих профилировщиков. Первоначально в этом посте говорилось, что JRuby был только на 20% медленнее, но с тех пор я представил оптимизацию, которая улучшила производительность MRI почти в два раза. 2, но вряд ли изменил производительность JRuby. Я подозреваю, что JVM выполнила ту же оптимизацию автоматически, поскольку она в основном составляла «постоянное сворачивание»)

1 Ответ

0 голосов
/ 05 мая 2018

Если вы выполняете вычисления на Integer с, а Integer s вписываются в native_word_size - 1 бит, тогда YARV будет использовать собственную машинную арифметику на Fixnum с. Если вы выполняете вычисления на Float с, работаете на 64-битной платформе и ваши вычисления укладываются в 62-битные вычисления, YARV будет использовать собственную арифметику FPU для flonums . В любом случае, это не намного быстрее, чем , если ваши операции не будут настолько тривиальными, что JIT JVM (или компилятор JRuby) может полностью их оптимизировать, постоянно сворачивать их или что-то подобное.

Лучшее место - Integer s, которые больше 63 бит, но меньше 64 бит, которые обрабатываются JRuby как целые числа машин, но не YARV, то же самое для Float s больше 62, но меньше 64 биты. В этом диапазоне JRuby будет использовать собственные операции, а YARV - нет, что дает JRuby преимущество в производительности.

В целом, YARV превосходит JRuby по задержке , особенно по времени запуска. Однако это во многом зависит от используемой JVM и среды. Существуют JVM, которые предназначены для очень быстрого запуска (например, IBM J9, для которой IMO должна быть настольной JVM по умолчанию вместо Oracle HotSpot) или Avian (которая на самом деле не является JVM, поскольку она реализует только подмножество JVM и JRE спецификации, но, тем не менее, может запускать многие нетривиальные программы, которые не используют ни одну из не реализованных функций, в том числе JRuby.) Кроме того, существуют среды и конфигурации, которые позволяют вам сохранять и повторно использовать JVM и экземпляр JRuby в памяти, исключающий значительную часть времени запуска.

Вторая важная вещь - это расширения YARV C. YARV имеет очень открытый и широкий API для расширений Си. По сути, расширения YARV C могут получить доступ практически ко всем частным внутренним деталям реализации YARV. (Это, очевидно, означает, что они могут повредить и привести к сбою YARV.) С другой стороны, «расширения C» JVM всегда должны проходить через барьер безопасности. Они могут испортить только память, явно переданную им Java-кодом, который их вызывает, они никогда не повредят другую память, не говоря уже о самой JVM. Однако это связано с затратами на производительность: вызов C из Java или наоборот * на 1021 * обычно медленнее, чем вызов C из YARV и наоборот.

Расширения YARV C на даже медленнее , чем это, поскольку JRuby, по сути, должен предоставить целый слой сложной эмуляции, эмулирующий внутренние структуры данных, функции и макет памяти YARV, чтобы получить хотя бы некоторые Расширения YARV C для запуска. Это просто медленно. Период.

Обратите внимание, что это не относится к оболочкам Ruby для библиотек C, которые используют Ruby FFI API. Они не зависят от внутренних компонентов YARV и, следовательно, не нуждаются в уровне эмуляции, а в JRuby реализована довольно быстрая и оптимизированная реализация Ruby FFI API. Стоимость мостового соединения JVM ↔ C по-прежнему применяется, однако.

Это две большие вещи, где YARV работает быстрее: код, который выполняется слишком коротко, чтобы воспользоваться преимуществами оптимизации JVM для долго выполняющихся процессов, и код, который интенсивно использует вызовы в и из C, особенно Расширения YARV C.

Если вы можете заставить свой код работать на TruffleRuby, это был бы интересный эксперимент. Оптимизация, которую может выполнять TruffleRuby, действительно удивительна (например, сворачивание всей библиотеки Ruby с использованием значительного количества динамического метапрограммирования, отражения и Hash поисков в одну константу), и она может приближаться и даже превосходить оптимизированный вручную C. Кроме того, TruffleRuby содержит интерпретатор C в дополнение к интерпретатору Ruby, и, таким образом, может анализировать и оптимизировать код Ruby, обращающийся к расширениям C и наоборот, и даже выполнять межязыковое встраивание, что означает, что в некоторых тестах он может выполнять код Ruby, интенсивно используя Расширения YARV быстрее чем YARV!

...