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!


Copyright 2006-2011 by Codemesh, Inc., ALL RIGHTS RESERVED

:
Java / C++ best practices
home products support customers partners newsroom about us contact us