230 likes | 674 Views
Java Native Interface (JNI). Phil Pratt-Szeliga Syracuse University. JNI Overview. JNI allows a developer to interop with arbitrary C/C++ code The managed to native transition can take time, so don’t expect a performance enhancement from, say, repeatedly incrementing an integer
E N D
Java Native Interface (JNI) Phil Pratt-Szeliga Syracuse University
JNI Overview • JNI allows a developer to interop with arbitrary C/C++ code • The managed to native transition can take time, so don’t expect a performance enhancement from, say, repeatedly incrementing an integer • With JNI you need to have a .dll built for Windows and a .so built for Linux (and I guess a .so for Mac). • Your build process needs to be able to built the .dllon Windows and the .so on Linux and then possibly pack it in the jar file • You need to build two separate dll’s and so’s to cover 32bit and 64bit platforms.
Calling JNI Code From Java package edu.syr.distobjects.jniexample; public class FirstExample{ //it is good to make public wrappers around //native methods public void printString(String str){ doPrintString(str); } //the native keyword it present here private native void doPrintString(String str); }
Creating the C/C++ Headers • Directory structure: • netbeans_root • build • classes • edu • syr • distobjects • jniexample • src • edu • syr • distobjects • jniexample • native (you need to make this directory) • Linux: • $ cd /path/to/netbeans_root/build/classes • $ javahedu.syr.distobjects.jniexample.FirstExample • $ mv edu_syr_distobjects_jniexample_FirstExample.h../../native/ • Windows: • $ cd \path\to\netbeans_root\build\classes • $ “C:\Program Files (x86)\Java\jkd1.6.0_26\bin\javah”edu.syr.distobjects.jniexample.FirstExample • $ move edu_syr_distobjects_jniexample_FirstExample.h..\..\native\
Created C/C++ Header /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class edu_syr_distobjects_jniexample_FirstExample */ #ifndef _Included_edu_syr_distobjects_jniexample_FirstExample #define _Included_edu_syr_distobjects_jniexample_FirstExample #ifdef __cplusplus extern "C" { #endif /* * Class: edu_syr_distobjects_jniexample_FirstExample * Method: doPrintString * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_edu_syr_distobjects_jniexample_FirstExample_doPrintString (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif
Copy Prototype to .cpp File #include “edu_syr_distobjects_jniexample_FirstExample.h” JNIEXPORT void JNICALL Java_edu_syr_distobjects_jniexample_FirstExample_doPrintString (JNIEnv* env, jobjectthis_obj, jstringstr) {} Note I gave names to env, this_obj, and str. env: Every JNI function is passed a JNIEnv pointer this_obj: Represents the managed “this” pointer str: The only argument to the method in the Java code
Implement a cout #include “edu_syr_distobjects_jniexample_FirstExample.h” #include <iostream> JNIEXPORT void JNICALL Java_edu_syr_distobjects_jniexample_FirstExample_doPrintString (JNIEnv * env, jobjectthis_obj, jstringstr) { intlen = (*env)->GetStringLength(env, str); char * nstr = new char[len]; (*env)->GetStringUTFRegion(env, str, 0, len, nstr); std::cout << nstr << std::endl; delete [] nstr;}
Build the .dll Put this in cl_options.txt (on one line) /I"C:\Program Files\Java\jdk1.6.0_26\include“ /I"C:\Program Files\Java\jdk1.6.0_26\include\win32“ FirstExample.cpp /DLL /OUT:first_example.dll /MACHINE:X64 Make a native_build.bat (keep lines separate) "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" amd64 cl @cl_options.txt
Load the .dll in Java package edu.syr.distobjects.jniexample; import java.io.File; public class FirstExample { //called during first initialization of FirstExampleclass static { //load requires an absolute path and this method ensures that File file = new File(“first_example.dll”); System.load(file.getAbsolutePath()); } public void printString(String str){ doPrintString(str); } private native void doPrintString(String str); }
Distributing the .dll with a .jar In netbeans if you create a package (folder) and then have your build process place the .dll in the folder, it will be included in the .jar public class BinaryLoader { public void extractBinary(String filename) throws Exception { String path = "/edu/syr/distobjects/jniexample/native/"+filename; InputStreamis = BinaryLoader.class.getResourceAsStream(path); OutputStreamos = new FileOutputStream(filename); while(true){ byte[] buffer = new byte[32*1024]; intlen = is.read(buffer); if(len== -1) { break; } os.write(buffer, 0, len); } os.flush(); os.close(); is.close(); } }
Saving State In a cpp • You can save state in a high performance way by using static globals in a cpp • But all instances of a class will share that state • A slower way is to save state back to a Java Field void setLongField(JNIEnv * env, jobjectobj, long value){ jclassthis_class = (*env)->GetObjectClass(env, obj); jfieldIDfid = (*env)->GetFieldID(env, this_class, “m_FieldName”, "J"); (*env)->SetLongField(env, obj, fid, value); }
Java Type Strings • Previously we passed in “J” into GetFieldID • B = byte • C = char • D = double • F = float • I = int • J = long • S = short • V = void • Z = boolean • Ljava/lang/String; = String • Ljava/lang/Object; = Object • Lfully/qualified/Classname = fully.qualified.Classsname • [B – single dimensional byte array • [[B – two dimensional byte array • [Ljava/lang/String; - one dimensional string array
Calling a Java Method jbyteArraylist_get(JNIEnv* env, jobjectlist, int index){ jmethodID mid; jclasslist_interface = (*env)->FindClass(env, "java/util/List"); mid = (*env)->GetMethodID(env, list_interface, "get", "(I)Ljava/lang/Object;"); return (*env)->CallObjectMethod(env, list, mid, index); } • “java/util/List” – interface or class name • “get” – method name • “(I)Ljava/lang/Object;” – a method that accepts an int as a parameter and returns a Ljava/lang/Object; • jbyteArray – a byte[] • In Java, the list was declared as: • List<byte[]> list;
JNI Field Functions • Based on Field Type: • GetObjectField • GetBooleanField • GetByteField • GetCharField • GetShortField • GetIntField • GetLongField • GetFloatField • GetDoubleField • Corresponding setters are like: • SetObjectField
JNI Method Invocation Functions • Based on return type: • CallObjectMethod • CallBooleanMethod • CallByteMethod • CallCharMethod • CallShortMethod • CallIntMethod • CallLongMethod • CallFloatMethod • CallDoubleMethod • CallVoidMethod
JNI Array Functions • Based on Array Type: • GetBooleanArrayElements • GetByteArrayElements • GetCharArrayElements • GetShortArrayElements • GetIntArrayElements • GetLongArrayElements • GetFloatArrayElements • GetDoubleArrayElements • Corresponding releases need to be called like: • ReleaseBooleanArrayElements • Array length: • GetArrayLength • Arrays of objects: can only get one at a time because you can’t know the size of an object to store in a regular C style array • GetObjectArrayElement • SetObjectArrayElement
Example of JNI Array Copy JNIEXPORT void JNICALL Java_edu_syr_pcpratts_rootbeer_ArrayMemory_doWriteIntArray (JNIEnv *env, jobjectthis_obj, jintArray array, jlong ref, jintlen){ char * dest = (char *) ref; jint* narray = (*env)->GetIntArrayElements(env, array, JNI_FALSE); memcpy(dest, narray, len*sizeof(int)); (*env)->ReleaseIntArrayElements(env, array, narray, JNI_ABORT); } jlong ref was previously allocated in the C++ code using new. You can easily save any pointer in C++ in a Java field using a long. ref was saved as a member field in the Java class. JNI_FALSE and JNI_ABORT have to deal with memory pinning and copying back of the change array into the java runtime. I can’t find a reference right now for these
Real World Usage of JNI • Rootbeer: tool to make it easier to program GPUs from Java • All the GPU vendors give C bindings to their API. • API allows to: • Create/Destroy GPU memory • Copy from CPU memory to GPU memory • Query how many sub-processors a GPU has • Compile CUDA programs to a binary for the GPU • Launch jobs onto the GPU
GPUs • GPU = Graphics Processing Unit • Specialized processor originally made only to feed Monitor with byte buffer • A device has on the order of 512 cores! • Each core is quite simple and slow: • No branch prediction or out of order execution • Clock rate is 1.3 Ghz range • Groups of 32 cores all have to be doing the same thing • They share instruction fetch hardware • Really hard to program • Need to learn special programming language to execute on the GPU (CUDA) • Need to manually serialize all of your complex classes in C/C++ to arrays (this may have changed recently…need to check) • Need to find huge amounts of parallelism in original program to get a speedup • Getting a speedup is the only reason to use a GPU • Naïve Dense matrix multiplication can easily be sped up 100X
Rootbeer • Rootbeer allows the developer to program in Java and it automatically: • Creates a CUDA program from analyzing Java Bytecode (with the help of the Soot Java Optimization Framework) • Creates Java Bytecode that can (de)serialize CPU memory to GPU memory in a high performance manner • Gives a Java interface for automatically launching these jobs on the GPU
Rootbeer Status • By the end of summer 2012 there will be a public release of Rootbeer available for people to use • We currently have a non-optimized version that is highly tested: • 20K SLOC product code • 5k SLOC test code • All tests pass covering all aspects of the Java programming language except: • Sleeping while inside a monitor • Reflection • Dynamic methods in Java (recently added to Java, makes static analysis really hard) • Java Code that uses JNI… • Garbage collection
References http://dev.kanngard.net/Permalinks/ID_20050509144235.html http://docs.oracle.com/javase/1.4.2/docs/guide/jni/spec/functions.html http://java.sun.com/docs/books/jni/html/jniTOC.html