What makes JNI calls slow?

46,626

Solution 1

First, it's worth noting that by "slow," we're talking about something that can take tens of nanoseconds. For trivial native methods, in 2010 I measured calls at an average 40 ns on my Windows desktop, and 11 ns on my Mac desktop. Unless you're making many calls, you're not going to notice.

That said, calling a native method can be slower than making a normal Java method call. Causes include:

  • Native methods will not be inlined by the JVM. Nor will they be just-in-time compiled for this specific machine -- they're already compiled.
  • A Java array may be copied for access in native code, and later copied back. The cost can be linear in the size of the array. I measured JNI copying of a 100,000 array to average about 75 microseconds on my Windows desktop, and 82 microseconds on Mac. Fortunately, direct access may be obtained via GetPrimitiveArrayCritical or NewDirectByteBuffer.
  • If the method is passed an object, or needs to make a callback, then the native method will likely be making its own calls to the JVM. Accessing Java fields, methods and types from the native code requires something similar to reflection. Signatures are specified in strings and queried from the JVM. This is both slow and error-prone.
  • Java Strings are objects, have length and are encoded. Accessing or creating a string may require an O(n) copy.

Some additional discussion, possibly dated, can be found in "Java(tm) Platform Performance: Strategies and Tactics", 2000, by Steve Wilson and Jeff Kesselman, in section "9.2: Examining JNI costs". It's about a third of the way down this page, provided in the comment by @Philip below.

The 2009 IBM developerWorks paper "Best practices for using the Java Native Interface" provides some suggestions on avoiding performance pitfalls with JNI.

Solution 2

It is worth mentioning that not all Java methods marked with native are "slow". Some of them are intrinsics that makes them extremely fast. To check which ones are intrinsic and which ones are not, you can look for do_intrinsic at vmSymbols.hpp.

Solution 3

Basically the JVM interpretively constructs the C parameters for each JNI call and the code is not optimized.

There are many more details outlined in this paper

If you are interested in benchmarking JNI vs native code this project has code for running benchmarks.

Solution 4

When talking about JNI, there are two directions: java calling C++, and C++ calling java. Java calling C++ (or C) via the "native" keyword is very fast, around 50 clock cycles. However, C++ calling Java is somewhat slow. We do a great deal of Java/C++ integration, and my rule of thumb is 1000 clock cycles per call, so you can get around 2M calls/second. I cannot answer your actual question of "why is it slow", but I'll hazard a guess that a lot of work has to be done to transfer arguments from the native C++ stack using varargs, onto the Java stack, validate whatever conformance is needed, and vice-versa on the return value.

However, also remember that once you make a call into a Java method from C++, if that method returns a complex data structure, you'll need to make JNI calls for all accesses into the result, as well. The same applies for converting complex C++ structure to Java. We've found in practice for example that it is much faster to serialize a C++ std::map<string,string> to JSON, hand the string across JNI, and have Java deserialize it into a Map<String,String>, assuming you want the entire map converted to Java.

Share:
46,626

Related videos on Youtube

skinnypinny
Author by

skinnypinny

Updated on August 03, 2022

Comments

  • skinnypinny
    skinnypinny almost 2 years

    I know that 'crossing boundaries' when making a JNI call in Java is slow.

    However I want to know what is it that makes it slow? What does the underlying jvm implementation do when making a JNI call that makes it so slow?

    • NPE
      NPE over 12 years
      (+1) Nice question. While we're on the subject, I would like to encourage anyone who has done actual benchmarks to post their findings.
    • Dave Newton
      Dave Newton over 12 years
      A JNI call needs to convert the Java objects passed in to something C (for example) can understand; same with the return value. Type conversion and call stack marshalling are a good chunk of it.
    • skinnypinny
      skinnypinny over 12 years
      Dave, I understand and have heard about that before. But what exactly is the conversion like? what is that 'something'? I am looking for details.
    • Vishy
      Vishy over 12 years
      Using direct ByteBuffers to pass data between Java and C can result in relatively low overhead.
    • bestsss
      bestsss over 12 years
      the call needs a proper C stack frame, pushing all useful CPU registers (and popping them back), the call needs fencing and also it prevents a lot of optimizations like inline. Also the threads has to leave the execution stack lock (for instance to allow biased locks to work while in native code) and then obtain it back.
  • skinnypinny
    skinnypinny over 12 years
    the paper you linked to seems more like a performance benchmark paper than one that describes how JNI internally works.
  • dmck
    dmck over 12 years
    @pdeva Unfortunately the other resources I found were linked to java.sun.com and the links have not been updated since the Oracle acquisition. I am looking for more details on the JNI internals.
  • A.H.
    A.H. over 12 years
    The paper is about Java 1.3 - quite a long time ago. Do the issues of that time still apply to Java 7?
  • A.H.
    A.H. over 12 years
    This answer claims, that some native code can be inlined by the JVM.
  • Andy Thomas
    Andy Thomas over 12 years
    That answer notes that some standard native code is inlined in the JVM rather than using JNI. Above, "native methods" refers to the general case of user-defined native methods implemented via JNI. Thanks for the pointer to sun.misc.Unsafe.
  • A.H.
    A.H. over 12 years
    I didn't want to claim, that this approach can be used for every JNI call. But it won't hurt to know that there is some middle ground between pure bytecode and pure JNI code. Perhaps this will affect some design decisions. Perhaps this mechanism is generalized in the future.
  • skinnypinny
    skinnypinny over 12 years
    I am awarding the bounty to this answer, yet I am keeping the question 'open' to attract anyone who wants to provide an even more detailed answer.
  • bestsss
    bestsss over 12 years
    @A.H, you mistake intrinsic w/ JNI. They are quite different. sun.misc.Unsafe and quite a lot of other stuff like System.currentTimeMillis/nanoTime are handled via 'magic' by the JVM. They are not JNI and they do not have proper .c/.h files at all, baring the JVM impl, itself. The approach can not be followed unless you are writing/hacking the JVM.
  • Philip Guin
    Philip Guin over 10 years
    "this java.sun.com document" is currently broken-- here's a working link.
  • Andy Thomas
    Andy Thomas almost 10 years
    @Philip - Thanks, updated the text above with your link.