HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Having fun with JNI: formatting a number

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
formattingnumberwithjnihavingfun

Problem

I attempted some (easy) coding with Java Native Interface. This is what I have:

six_pack_Neatifier.h:

(autogenerated by javah)

#include 

#ifndef INCLUDED_SIX_PACK_NEATIFIER
#define INCLUDED_SIX_PACK_NEATIFIER

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     six_pack_Neatifier
 * Method:    neatify
 * Signature: (JCI)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_six_pack_Neatifier_neatify(JNIEnv*, 
                                                          jclass, 
                                                          jlong, 
                                                          jchar, 
                                                          jint);

#ifdef __cplusplus
}
#endif

#endif // INCLUDED_SIX_PACK_NEATIFIER


six_pack_Neatifier.cpp:

#include 

#include "six_pack_Neatifier.h"

JNIEXPORT jstring JNICALL Java_six_pack_Neatifier_neatify(JNIEnv* env, 
                                                          jclass clazz, 
                                                          jlong val, 
                                                          jchar pad, 
                                                          jint span)
{
    // Convert 'val' to a string.
    std::string number_string;
    std::stringstream strstream;
    strstream > number_string;

    const char* raw = number_string.c_str();
    const int signlen = val = signlen) 
    {
        out[pos] = (outlen - 1 - pos) % ospan == 0 ?
                                               pad :
                                               raw[src--];
        --pos;
    }

    if (val NewStringUTF(out);
    delete[] out;
    return ret;
}


Makefile (MacOSX):

leabnit.jnilib: six_pack_Neatifier.o
    g++ -dynamiclib -o libneat.jnilib six_pack_Neatifier.o

six_pack_Neatifier.o: six_pack_Neatifier.cpp
    g++ -std=c++11 -O3 -I/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/ -c six_pack_Neatifier.cpp


Neatifier.

Solution

JNI, cool, but why? Are you expecting C++ to be faster?

In an independent implementation, you may be right, a raw C++ implementation that has no JNI component may well be faster, but, the overheads in the interaction between Java and C++ are expensive. Each time there is a call between the systems, there needs to be a translation of all data passed, and returned. If there is a lot of work to be done on the C++ side, then the overhead is quickly amortized, and becomes "worth it". If the C++ side is fast, though, then the bulk of the time is spent "in translation".

In your case, as I suspected, the overhead far exceeds the actual time-to-format the numbers.
Review

So, about the review:

  • The header-file is auto-generated, and is not really your code.



  • In the implementation, you do ... horrible things, like you convert the inptut to a C++ string, but then convert it again back to a C char*. The rest of the implementation is about what I would like to see (hey, I recognize that code.... ;-)



  • The Java side looks only OK. I don't like the absolute path for the library load... you should use the loadLibrary(...) call instead and ensure your library is on the library load path.



  • You don't print the exception on a library load error, just the toString(). Losing exception data (and a possible cause) like that is... silly. Log the exception, or do a ex.printStackTrace();



Performance

I compared your JNI version against other versions from previous questions. To do this, I pulled the code on to a linux machine. There are two interesting things here....

  • the version of code I recommended in my previous answer is still faster than your code, now on linux too...



  • the JNI is slow in comparison.



Here's the commandline I used (note, I removed the package declaration...):

g++ -std=c++11 -O3 -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -shared -fPIC -o libneat.so Neatifier.cpp


Then, I added it to my UBench code, as:

public static String neatifyJNI(final long val) {
    return neatifyJNI(val, ' ', 3);
}

public static String neatifyJNI(final long val, final char pad, final int span) {
    return Neatifier.neatify(val, pad, span);
}


And my performance results are:

Task NumberPad -> OP: (Unit: MILLISECONDS)
  Count    :    10000      Average  :   0.1633
  Fastest  :   0.1376      Slowest  :   2.7011
  95Pctile :   0.2449      99Pctile :   0.3013
  TimeBlock : 0.190 0.155 0.152 0.156 0.156 0.147 0.158 0.219 0.149 0.150
  Histogram :  9810   146    33     9     2

Task NumberPad -> RL: (Unit: MILLISECONDS)
  Count    :    10000      Average  :   0.2480
  Fastest  :   0.2251      Slowest  :   3.0615
  95Pctile :   0.2732      99Pctile :   0.7174
  TimeBlock : 0.314 0.236 0.237 0.241 0.239 0.241 0.235 0.241 0.241 0.254
  Histogram :  9796   189    13     2

Task NumberPad -> RLP: (Unit: MILLISECONDS)
  Count    :    10000      Average  :   0.1228
  Fastest  :   0.1163      Slowest  :   2.7432
  95Pctile :   0.1398      99Pctile :   0.1720
  TimeBlock : 0.130 0.119 0.119 0.120 0.122 0.124 0.124 0.123 0.123 0.124
  Histogram :  9972    20     4     3     1

Task NumberPad -> JNI: (Unit: MILLISECONDS)
  Count    :    10000      Average  :   1.0328
  Fastest  :   0.9734      Slowest  :   5.3716
  95Pctile :   1.0931      99Pctile :   1.1412
  TimeBlock : 1.054 1.046 1.045 1.063 1.037 1.019 1.008 1.026 1.009 1.021
  Histogram :  9997     2     1


in essence, it is ... 5 times slower than other options.

Here are some general disadvantages for JNI:

  • Overhead of translation



  • not able to inline the code by the JIT compiler



  • portability

Code Snippets

g++ -std=c++11 -O3 -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -shared -fPIC -o libneat.so Neatifier.cpp
public static String neatifyJNI(final long val) {
    return neatifyJNI(val, ' ', 3);
}

public static String neatifyJNI(final long val, final char pad, final int span) {
    return Neatifier.neatify(val, pad, span);
}
Task NumberPad -> OP: (Unit: MILLISECONDS)
  Count    :    10000      Average  :   0.1633
  Fastest  :   0.1376      Slowest  :   2.7011
  95Pctile :   0.2449      99Pctile :   0.3013
  TimeBlock : 0.190 0.155 0.152 0.156 0.156 0.147 0.158 0.219 0.149 0.150
  Histogram :  9810   146    33     9     2

Task NumberPad -> RL: (Unit: MILLISECONDS)
  Count    :    10000      Average  :   0.2480
  Fastest  :   0.2251      Slowest  :   3.0615
  95Pctile :   0.2732      99Pctile :   0.7174
  TimeBlock : 0.314 0.236 0.237 0.241 0.239 0.241 0.235 0.241 0.241 0.254
  Histogram :  9796   189    13     2

Task NumberPad -> RLP: (Unit: MILLISECONDS)
  Count    :    10000      Average  :   0.1228
  Fastest  :   0.1163      Slowest  :   2.7432
  95Pctile :   0.1398      99Pctile :   0.1720
  TimeBlock : 0.130 0.119 0.119 0.120 0.122 0.124 0.124 0.123 0.123 0.124
  Histogram :  9972    20     4     3     1

Task NumberPad -> JNI: (Unit: MILLISECONDS)
  Count    :    10000      Average  :   1.0328
  Fastest  :   0.9734      Slowest  :   5.3716
  95Pctile :   1.0931      99Pctile :   1.1412
  TimeBlock : 1.054 1.046 1.045 1.063 1.037 1.019 1.008 1.026 1.009 1.021
  Histogram :  9997     2     1

Context

StackExchange Code Review Q#90487, answer score: 6

Revisions (0)

No revisions yet.