patternjavaMinor
Having fun with JNI: formatting a number
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
six_pack_Neatifier.cpp:
Makefile (MacOSX):
Neatifier.
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_NEATIFIERsix_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.cppNeatifier.
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:
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....
Here's the commandline I used (note, I removed the package declaration...):
Then, I added it to my UBench code, as:
And my performance results are:
in essence, it is ... 5 times slower than other options.
Here are some general disadvantages for JNI:
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 Cchar*. 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 aex.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.cppThen, 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 1in 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.cpppublic 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 1Context
StackExchange Code Review Q#90487, answer score: 6
Revisions (0)
No revisions yet.