Что делает JNI звонки медленными?


194

Я знаю, что «пересечение границ» при вызове JNI в Java происходит медленно.

Однако я хочу знать, что делает его медленным? Что делает базовая реализация jvm при выполнении вызова JNI, который делает его таким медленным?


2
(+1) Хороший вопрос. Пока мы обсуждаем эту тему, я хотел бы призвать всех, кто выполнил реальные тесты, публиковать свои выводы.
— NPE

2
Вызов JNI должен преобразовать переданные Java-объекты во что-то, что C (например) может понять; то же самое с возвращаемым значением. Преобразование типов и маршаллинг стека вызовов - хороший кусок этого.
— Дэйв Ньютон,

Дэйв, я понимаю и слышал об этом раньше. Но как именно происходит преобразование? что это за "что-то"? Я ищу детали.
— Пдева

Использование прямых ByteBuffers для передачи данных между Java и C может привести к относительно низким издержкам.
— Питер Лоури

6
вызов требует правильного фрейма стека C, выдвигая все полезные регистры процессора (и возвращая их обратно), вызов нуждается в ограждении, а также он предотвращает много оптимизаций, таких как inline. Кроме того, потоки должны оставить блокировку стека выполнения (например, чтобы позволить смещенным блокировкам работать в то время как в нативном коде), а затем получить ее обратно.
— bestsss

Ответы:


174

Во-первых, стоит отметить, что под «медленным» мы говорим о чем-то, что может занять десятки наносекунд. Что касается простых нативных методов, в 2010 году я измерял вызовы в среднем на 40 нс на моем рабочем столе Windows и 11 нс на моем рабочем столе Mac. Если вы не делаете много звонков, вы не будете замечать.

Тем не менее, вызов нативного метода может быть медленнее, чем обычный вызов метода Java. Причины включают в себя:

  • Собственные методы не будут встроены JVM. И при этом они не будут точно скомпилированы для этой конкретной машины - они уже скомпилированы.
  • Массив Java может быть скопирован для доступа в нативный код, а затем скопирован обратно. Стоимость может быть линейной по размеру массива. Я измерил JNI- копирование массива 100 000, чтобы он составлял в среднем около 75 микросекунд на моем рабочем столе Windows и 82 микросекунды на Mac. К счастью, прямой доступ может быть получен через GetPrimitiveArrayCritical или NewDirectByteBuffer .
  • Если метод передан объекту или ему необходимо выполнить обратный вызов, то нативный метод, скорее всего, будет делать свои собственные вызовы JVM. Доступ к полям, методам и типам Java из нативного кода требует чего-то похожего на рефлексию. Подписи указываются в строках и запрашиваются из JVM. Это и медленно, и подвержено ошибкам.
  • Строки Java являются объектами, имеют длину и кодируются. Для доступа или создания строки может потребоваться копия O (n).

Некоторое дополнительное обсуждение, возможно, датированное, может быть найдено в «Производительности платформы Java: стратегии и тактики», 2000, Стивом Уилсоном и Джеффом Кессельманом, в разделе «9.2: Изучение затрат JNI». Это примерно треть пути вниз по этой странице , предоставленная в комментарии @Philip ниже.

В документе IBM developerWorks 2009 года "Лучшие практики использования собственного интерфейса Java" содержатся некоторые предложения по предотвращению ошибок производительности при использовании JNI.


1
Этот ответ утверждает, что JVM может встроить некоторый нативный код .
— AH

5
В этом ответе отмечается, что некоторый стандартный нативный код встроен в JVM, а не в JNI. Выше «нативные методы» относятся к общему случаю пользовательских нативных методов, реализованных через JNI. Спасибо за указатель на sun.misc.Unsafe.
— Энди Томас

Я не хотел утверждать, что этот подход можно использовать для каждого вызова JNI. Но это не помешает знать , что там есть какое - то среднее между чистым байткодом и чистым кодом JNI. Возможно, это повлияет на некоторые дизайнерские решения. Возможно, этот механизм будет обобщен в будущем.
— AH

3
@ А-а, ты ошибаешься по отношению к JNI. Они совсем разные. sun.misc.Unsafeи многие другие вещи System.currentTimeMillis/nanoTimeобрабатываются JVM с помощью «магии». Они не являются JNI, и у них вообще нет надлежащих файлов .c / .h, за исключением самого JVM. Подход не может быть использован, если вы не пишете / не взламываете JVM.
— bestsss

1
« этот документ java.sun.com » в настоящее время не работает - вот рабочая ссылка.
— Филипп Гуин

25

Стоит отметить, что не все методы Java, помеченные nativeкак «медленные». Некоторые из них являются внутренними, что делает их чрезвычайно быстрыми. Чтобы проверить, какие из них являются внутренними, а какие нет, вы можете посмотреть do_intrinsicна vmSymbols.hpp .


23

По сути, JVM интерпретирует параметры C для каждого вызова JNI, и код не оптимизируется.

Есть много других деталей, изложенных в этой статье

Если вы заинтересованы в бенчмаркинге JNI по сравнению с собственным кодом, у этого проекта есть код для запуска бенчмарков.


2
документ, на который вы ссылаетесь, больше похож на тест производительности, чем на тот, который описывает внутреннюю работу JNI.
— Пдева

@pdeva К сожалению, другие ресурсы, которые я нашел, были связаны с java.sun.com, и ссылки не обновлялись с момента приобретения Oracle. Я ищу более подробную информацию о внутренних органах JNI.
— DMK

13
Статья о Java 1.3 - довольно давно. Проблемы того времени все еще относятся к Java 7?
— AH
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.