Tuesday, February 15, 2011

Break the Language Barrier Series - Chapter Java

Recently I was asked to do some work on JNI for Java programmers to have low-level access
to the underlying systems, such as Win32 API and Cocoa Framework.
Then I ran into some interesting technologies called JNA and JInvoke, both of which claim to provide dll import for Java. However, on the official website of JInvoke they claim to be 10X faster than JNA. According to them, it compares to the performance of JNI, and even native C code's LoadLibrary from Win32 API.
Since I've been so curious about their statement, I decided to run a benchmark on all 3 approaches. To achieve this, we would create a very simple function that takes 5 input parameters and returns a sum of 5 numbers. The reason behind this is that the performance is only measurable from invoking the function and observing its overhead. So now let's take a look at all 3 implementations:

1. JNA
package com.talent;
import com.sun.jna.Library;
import com.sun.jna.Native;
public class MyJNA {
 public static int nLoops = 5000000;
 public interface CLibrary extends Library{
  int fnSpeedtest(int a, int b, int c, int d, int e);
 }
 public static void main(String[] args)
 {
  CLibrary clib = (CLibrary) Native.loadLibrary("C:/jni/Speedtest.dll", CLibrary.class);
  long startTime = System.currentTimeMillis();
  for(int i = 0 ; i < nLoops; i ++)
  {
   int sum = clib.fnSpeedtest(1, 2, 3, 4, 5);
  }
  long timeElapsed = System.currentTimeMillis() - startTime;
  System.out.println(nLoops + " Loops took JNA " + timeElapsed + " milliseconds to complete.");
 }
}
2. JInvoke
package com.talent;
import com.jinvoke.CallingConvention;
import com.jinvoke.JInvoke;
import com.jinvoke.NativeImport;
public class MyJInvoke {
 public static int nLoops = 5000000;
 @NativeImport(library="C:/jni/Speedtest.dll", convention=CallingConvention.CDECL)
 public static native int fnSpeedtest(int a, int b, int c, int d, int e);
 public static void main(String[] args)
 {
  JInvoke.initialize();
  long startTime = System.currentTimeMillis();
  for(int i = 0 ; i < nLoops; i ++)
  {
   int sum = fnSpeedtest(1,2,3,4,5);
  }
  long timeElapsed = System.currentTimeMillis() - startTime;
  System.out.println(nLoops + " Loops took JInvoke " + timeElapsed + " milliseconds to complete.");
 }
}

3. JNI
package com.talent;
import java.io.*;

class SpeedtestJNI{
 public static int nLoops = 5000000;
 static {
  try{
   System.loadLibrary("Speedtest");
                }
                catch(UnsatisfiedLinkError e){       
                 System.out.println(e.getMessage());
                 e.printStackTrace();   
                }
        }
        public static native int fnSpeedtest(int a, int b, int c, int d, int e);
        
        public static void main(String s[]){
         long startTime = System.currentTimeMillis();
         for(int i = 0 ; i < nLoops; i++)
         {
          int sum = fnSpeedtest(1,2,3,4,5);
         }
         long timeElapsed = System.currentTimeMillis() - startTime;
  System.out.println(nLoops + " Loops took JNI " + timeElapsed + " milliseconds to complete.");
        }
}

Finally the chart that compares the 3 of them:

5000000 Loops took JNA 43656 milliseconds to complete.
5000000 Loops took JInvoke 5615 milliseconds to complete.
5000000 Loops took JNI 297 milliseconds to complete.

I don't understand the nature of this performance discrepancy, but it still holds true that JInvoke is about 8 times faster than JNA. Although it's nowhere comparable to JNI, let alone pure C code. Interested readers can get the dll file here.
In the following weeks(hopefully) I will also explore the differences between C++ Interop, PInvoke, and COM Interop, when it comes to .NET calling C libraries.

No comments: