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

194

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

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

pdeva
источник
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 .

Tema
источник
23

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

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

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

dmck
источник
2
документ, на который вы ссылаетесь, больше похож на тест производительности, чем на тот, который описывает внутреннюю работу JNI.
Пдева
@pdeva К сожалению, другие ресурсы, которые я нашел, были связаны с java.sun.com, и ссылки не обновлялись с момента приобретения Oracle. Я ищу более подробную информацию о внутренних органах JNI.
DMK
13
Статья о Java 1.3 - довольно давно. Проблемы того времени все еще относятся к Java 7?
AH