Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java Tip 141: Fast math with JNI

Use JNI to dramatically speed math in J2SE 1.4

  • Print
  • Feedback
While developing a computer-generated hologram (CGH) program, I noticed that the math routines in Java 2 Platform, Standard Edition (J2SE) 1.4x were several times slower than the corresponding routines in J2SE 1.3.1. Sun Microsystems has implemented a new math package, StrictMath, in J2SE 1.4 that guarantees identical math calculations across all platforms at the expense of execution speed. My CGH program ran seven times slower with the new J2SE 1.4x routines! I didn't want to switch back to J2SE 1.3.1, however, because I wanted to use the new javax.imageio package to save my holograms to disk as .png files. I submitted some bug reports to Sun about this serious performance regression, but received no meaningful response.

JNI to the rescue

Since my trusty C++ compiler produced fast math code, I just needed to call these math routines instead of the slow StrictMath routines from my Java program. Java Native Interface (JNI) enables Java code to interface with native code written in other languages like C/C++. It enables you to write the bulk of your application in Java and still utilize highly optimized native code where necessary. And in the corporate computing world, JNI enables Java applications to connect to legacy code that might be difficult or impractical to port to Java.

To build my JNI math library, I first create a Java class, MathLib, which declares three native methods, cos(), sin(), and sqrt(). I add a main method to test these native methods:

package com.softtechdesign.math;
/**
 * MathLib declares native math routines to get around the slow StrictMath math
 * routines in JDK 1.4x
 * @author Jeff S Smith
 */
public class MathLib
{
    public static native double sin(double radians);
    public static native double cos(double radians);
    public static native double sqrt(double value);
    static 
    {
        System.loadLibrary("MathLib");
    }
    
    public static void main(String[] args) 
    {
        System.out.println("MathLib benchmark..." + new java.util.Date());
        double d = 0.5;
        for (int i=0; i < 10000000; i++)
        {
            d = (d * i) / 3.1415926;
            d = MathLib.sin(d);
            d = MathLib.cos(d);
            d = MathLib.sqrt(d) * 3.1415926;
        }
        
        System.out.println("Benchmark done. Time is " + new java.util.Date());
    }
}


The static initializer, which executes before the code in the main method, loads the MathLib library. The main method performs a benchmark test of the routines by calling them in a loop. Next, I compile my class and create a header (.h) file for it by using the standard javah tool:

javah -jni -o MathLib.h -classpath . com.softtechdesign.math.MathLib


This command creates a file called MathLib.h that contains the following C function signatures: cos(), sin(), and sqrt(). This C header file includes <jni.h>, the standard header file for JNI applications. Note how the fully qualified class name becomes part of the C function name. MathLib.h looks like this:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_softtechdesign_math_MathLib */
#ifndef _Included_com_softtechdesign_math_MathLib
#define _Included_com_softtechdesign_math_MathLib
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_softtechdesign_math_MathLib
 * Method:    sin
 */
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_sin
  (JNIEnv *, jclass, jdouble);
/*
 * Class:     com_softtechdesign_math_MathLib
 * Method:    cos
 */
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_cos
  (JNIEnv *, jclass, jdouble);
/*
 * Class:     com_softtechdesign_math_MathLib
 * Method:    sqrt
 */
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_sqrt
  (JNIEnv *, jclass, jdouble);
#ifdef __cplusplus
}
#endif
#endif


Next, I create a file called Math.c and implement the native C code that includes the three math routines using the signatures found in my MathLib.h file. Note that these routines simply call the standard C versions of sin(), cos(), and sqrt():

#include <jni.h>
#include <math.h>
#include "MathLib.h"
#include <stdio.h>
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_sin(JNIEnv *env, jobject obj, jdouble value)
{
    return(sin(value));
}
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_cos(JNIEnv *env, jobject obj, jdouble value)
{
    return(cos(value));
}
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_sqrt(JNIEnv *env, jobject obj, jdouble value)
{
    return(sqrt(value));
}


The routines all return a jdouble and have two standard JNI parameters, JNIEnv (JNI interface pointer) and jobject (a reference to the calling object itself, similar to this). All implemented JNI routines have these first two parameters (even if the native method doesn't declare any parameters). The third parameter is my user-defined parameter that contains the value I pass into the function. These routines calculate the sin(), cos(), or sqrt() of this parameter and return the value as a jdouble.

A word about JNI types

Native methods can access the following types:

Boolean  jboolean
string   jstring
byte     jbyte
char     jchar
short    jshort
int      jint
long     jlong
float    jfloat
double   jdouble
void     void 


All of these types can be directly accessed except for jstring, which requires a subroutine call to convert a Java Unicode string (2 bytes) to a C-style char* string (1 byte UTF-8 format). To convert a jstring to a C-style string, you might write code like the following:

JNIEXPORT void JNICALL
Java_MyJavaClass_printName(JNIEnv *env, jobject obj, jstring name)
{
    const char *str = (*env)->GetStringUTFChars(env, name, 0);
    printf("%s", name);
      //Need to release this string when done with it
      //to avoid memory leak
    (*env)->ReleaseStringUTFChars(env, name, str);
}


You can use the (*env)->NewStringUTF() function to create a new jstring from a C-style string. For example, a C function can contain the following code:

JNIEXPORT jstring JNICALL
Java_MyJavaClass_getName(JNIEnv *env, jobject obj)
{
    return (*env)->NewStringUTF(env, "Fred Flintstone");
}


Back to finishing MathLib

Continuing with MathLib, I compile my C code into a library (a Dynamic Link Library (DLL) since my code runs under Windows). I use Borland C++ Builder to create my DLL. If you use C++ Builder, make sure you create a DLL project (named MathLib) and then add the Math.c file to it. You also need to add the following to your compiler's include path:

  \javadir\include
  \javadir\include\win32


C++ Builder creates a library named MathLib.dll when it builds this project.

If you compile a Windows C library using Visual C++, you compile it like so:

cl -Ic:\javadir\include -Ic:\javadir\include\win32 -LD Math.c -FeMathLib.dll


And if you compile a C library under Solaris, you use the following command:

cc -G -I/javadir/include -I/javadir/include/solaris Math.c -o MathLib.so


The final step entails adding my DLL directory to my Java runtime library path (alternatively, I can copy the DLL to my Java program's working directory). If the Java code can't find the library, you will get a java.lang.UnsatisfiedLinkError. Try running your Java program:

  • Print
  • Feedback

Resources