Coding guidelines
Introduction
There are a number of recommendations for writing industrial-strength mixed Java/C++ applications. This document provides an unordered list of generally useful tips and techniques.
JVM Loading
Use on-demand loading for prototyping but not for production
It is easy to write code like this:
int main( int argc, char * argv[] )
{
xmog_jvm_loader::setConfigFile( "./app.config" );
MyJavaType myt( argv[ 1 ] );
...
}
This could actually work if the app.config file existed and contained the correct information. It could also fail spectacularly if either of these conditions were false. For a production quality application, you really want to decouple the JVM loading from the Java constructor invocation.
Consider the following alternative implementation:
int main( int argc, char * argv[] )
{
try
{
xmog_jvm_loader::setConfigFile( "./app.config" );
xmog_jvm_loader & loader =
xmog_jvm_loader::get_jvm_loader();
xmog_jvm * pJvm = loader.load();
if( pJvm == NULL )
{
cerr << "couldn't load JVM" << endl;
return -1;
}
}
catch( xmog_exception & xe )
{
cerr << "exception on loading" << endl;
return -1;
}
MyJavaType myt( argv[ 1 ] );
...
}
Now you have totally decoupled the JVM loading from the use of the Java type MyJavaType. This allows you to provide much better error feedback.
Performance anti-patterns
Avoid deep object references and unnecessary calls
Each transition across the language boundary has a measurable overhead. While you can mostly ignore this overhead, there is no reason to slow down your application unnecessarily. Consider the following C++ code snippet:
String temp = "this is a test";
cout << temp.toUpperCase().substring( 0, 4 ).to_chars() <<
temp.toUpperCase().substring( 6, 8 ).to_chars() <<
temp.toUpperCase().substring( 8, 9 ).to_chars() << endl;
What's wrong with this? Well, it works perfectly, so technically there's nothing wrong with it. In terms of performance though, it is a truly awful piece of code. If this code were written in Java, the javac compiler might already have optimized some of it and the runtime would definitely optimize it some more, but in our case, we're calling these Java methods from C++ and the Java side cannot optimize anything! What you see is what is being executed. We are performing three toUpperCase() invocations on the same string and then we have three substring() invocations across the language boundary and finally we unmarshal three Java strings across the language boundary via the to_chars() function.
As a first step, this snippet could be written much more efficiently by not unnecessarily invoking toUpperCase() three times:
String temp = "this is a test";
String upper = temp.toUpperCase();
cout << upper.substring( 0, 4 ).to_chars() <<
upper.substring( 6, 8 ).to_chars() <<
upper.substring( 8, 9 ).to_chars() << endl;
This is a simple technique and it seems so obvious. We're stressing this technique because compilers and the Java runtime let you get away with writing inefficient source code by cleaning up many of your "performance bugs" for you. This automatic performance fixing will not occur when the calls come from across the language boundary, so you'll have to think of it.
As a second step, if this is a truly performance critical piece of code, you might consider getting rid of the substring() operations by doing one to_chars() on the string and then performing the substring operations entirely in C++.
The other place where calls across the boundary could lead to a performance issue is with nested object references. Consider this snippet:
String tempStr = foo.bar.z.str; int tempLen = foo.bar.z.str.length();
The first access of the deeply nested str instance might be unavoidable, but the second one is completely unnecessary. This snippet should be rewritten as:
String tempStr = foo.bar.z.str; int tempLen = tempStr.length();
This will save three JNI field dereferences and improve the performance of this snippet dramatically.
Array element access
Consider the following snippet:
xmog_java_int_array intArr( 100 );
for( int i=0; i<intArr.length; i++ )
intArr[ i ] = 0;
This works perfectly well, but it is dead-slow because you're performing JNI operations in the loop. Compare this to the following snippet:
int nativeArr[ 100 ];
for( int i=0; i<100; i++ )
nativeArr[ i ] = 0;
xmog_java_int_array intArr( nativeArr, 100 );
Here, you're performing the initialization completely on the C++ side, thereby even allowing the C++ compiler to apply its optimizations, and then there is one (it's actually two) JNI call to create a Java integer array initialized from the native array. This performs much better than the first alternative!
