Search code examples
javaconditional-compilationcompile-time

any mechanism in Java to provide compile-time code variants?


I am writing a set of visual interfaces to data structures in Java. The idea is that these classes should be high performance implementations of algorithms, but with embedded hooks so that the algorithms can be displayed interactively.

There are a number of reasons to do this, but if you will accept this request at face value, I want to embed calls in the middle of algorithms to identify that a particular subsection has just been done. For example, one pass of a sorting algorithm.

I would prefer that the library be both very efficient and allow this. In C++, I would plug in two different templates or use conditional compilation, and two versions of the code could reasonably be generated. Is there any way to achieve this in Java? I'm hoping someone else can come up with one because I can't.

NEWS FLASH. I tried this actual code.

For n=100,000 an insertion sort takes approximately 9800 ms with VISUALIZE as a static variable but not final, vs. about 3100 with it commented out. So the performance loss is unacceptable.

With visualize as static final, the optimizer does indeed detect it and chop it out, but given that it's final what can I do with it? I can't dynamically turn visualization on and off!

public class TestSort {
    private static boolean VISUALIZE = false; 
    private static ArrayObserver ao;
    public static void insertionSort(int[] x) {
        for (int i = 1; i < x.length; i++) {
            int temp = x[i];
            int j = i - 1;
            if (x[j] > temp) {
                do {    
                    x[j+1] = x[j];
/*              if (VISUALIZE) {
                        ao.compare(i-1, i);
            ao.copy(i-1, i);
            }*/
                } while (--j >= 0 && x[j] > temp);
                x[j+1] = temp;
            }
        }
    }
    static Random r = new Random();
    static int[] createRandomArray(int n) {
    int[] x = new int[n];
    for (int i = 0; i < x.length; i++)
        x[i] = r.nextInt(100);
        return x;
    }
    static void display(int[] x) {
    for (int i = 0; i < x.length; i++)
        System.out.print(x[i] + " ");
        System.out.println();
    }
    public static void main(String args[]) {
    //int[] x = {9, 8, 7, 6, 5, 4, 3, 2, 1};
    int [] x = createRandomArray(100000);
    ao = new ArrayObserver(x);
    if (x.length < 20) display(x);
    long t0 = System.currentTimeMillis();
    insertionSort(x);
    long t1 = System.currentTimeMillis();
    if (x.length < 20) display(x);
    System.out.println(t1-t0);  
    }
}

Solution

  • I think you have a few options:

    • Put a test in on a static final constant (e.g. a boolean), which conditionally executes the the visual interface code. The JVM will eliminate the dead code when you set the constant to "off", and your code will be fully optimised. The downside is, of course, that you can only switch this at compile time, but this may be fine if you actually want to build two copies of the library.
    • Add an extra parameter to the function that determines whether the visual interface is called, and test this where necessary in your algorithm. This will add a small amount of runtime overhead, but may well be acceptable. I suggest you benchmark this, in my experience though such tests on a local variable are usually sufficiently cheap that you can get away with it (in CPU terms, will probably be just a register test which is likely to be even cheaper than the cost of a single memory access into an int[] array...)
    • Use a higher level / meta-language to express the algorithms, and use code-generation techniques to generate the actual code you need (with or without). I've done stuff like this in Clojure, for example. It's also an option to generate bytecode directly with tools like ASM (if all you care about is execution, and don't need the Java source code).
    • Use a textual pre-processor. It should work fine for Java code generation (as it does for C/C++), though it's not such a common approach and you may find it a bit fragile. You may need to do some clever stuff like generating different class names in the file system etc.