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.

\$\endgroup\$
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
\$\endgroup\$
  • \$\begingroup\$ I had to see... for mysefl... how... JNI... performs. ;-) \$\endgroup\$ – coderodde May 12 '15 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\$ – rolfl May 12 '15 at 15:39
  • \$\begingroup\$ Of course. You thought I know what "expected" is? \$\endgroup\$ – coderodde May 12 '15 at 15:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.