6
\$\begingroup\$

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

six_pack_Neatifier.h:

(autogenerated by javah)

#include <jni.h>
#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 <sstream>
#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 << val;
 strstream >> number_string;
 const char* raw = number_string.c_str();
 const int signlen = val < 0;
 const size_t digitlen = number_string.length() - signlen;
 // +1 for the C string null terminator.
 const size_t outlen = signlen + digitlen + (digitlen - 1) / span + 1;
 char *const out = new char[outlen];
 // Terminate the C string.
 out[outlen - 1] = '0円';
 int pos = outlen - 2;
 int src = number_string.size() - 1;
 const size_t ospan = span + 1;
 while (pos >= signlen) 
 {
 out[pos] = (outlen - 1 - pos) % ospan == 0 ?
 pad :
 raw[src--];
 --pos;
 }
 if (val < 0) 
 {
 out[0] = '-';
 }
 jstring ret = env->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.java:

package six.pack;
import java.io.File;
import java.util.Scanner;
/**
 * This class implements a couple of digit grouping routines.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6
 */
public class Neatifier {
 /**
 * Try load the native library.
 */
 static {
 try {
 System.load(System.getProperty("user.dir") + File.separator +
 "src" + File.separator + "libneat.jnilib");
 } catch (final UnsatisfiedLinkError ule) {
 System.err.println("Could not load the native library. " + ule);
 System.exit(-1);
 } 
 }
 /**
 * Returns neat string representation of <code>val</code> using 
 * <code>pad</code> as the padding character, and groups of length 
 * <code>span</code>. Uses Rolfl's algorithm implemented in C++.
 * 
 * @param val the number to print neatly.
 * @param pad the padding character.
 * @param span the length of a digit group.
 * @return a neat string.
 */
 public static native String neatify(final long val,
 final char pad,
 final int span);
 /**
 * The entry point into a program.
 * @param args the command line arguments.
 */
 public static void main(final String... args) {
 final Scanner scanner = new Scanner(System.in);
 while (scanner.hasNextLong()) {
 final long l = scanner.nextLong();
 System.out.println(neatify(l, '_', 3));
 } 
 }
}

Tell me anything that comes to mind.

200_success
146k22 gold badges190 silver badges479 bronze badges
asked May 12, 2015 at 7:06
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

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....

  1. the version of code I recommended in my previous answer is still faster than your code, now on linux too...
  2. 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:

  1. Overhead of translation
  2. not able to inline the code by the JIT compiler
  3. portability
answered May 12, 2015 at 15:34
\$\endgroup\$
3
  • \$\begingroup\$ I had to see... for mysefl... how... JNI... performs. ;-) \$\endgroup\$ Commented May 12, 2015 at 15:39
  • \$\begingroup\$ JNI performs as expected. Sometimes it is worth it, sometimes it is not. Your use case sucks for that, though \$\endgroup\$ Commented May 12, 2015 at 15:39
  • \$\begingroup\$ Of course. You thought I know what "expected" is? \$\endgroup\$ Commented May 12, 2015 at 15:41

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.