Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java compiler optimizations with final local variables

I always thought final keyword has no effect, performancewise, on local method variables or parameters. So, I tried to test the following code and it seems I was wrong:

private static String doStuffFinal() {
    final String a = "A";
    final String b = "B";
    final int n = 2;
    return a + b + n;
}

private static String doStuffNotFinal() {
    String a = "A";
    String b = "B";
    int n = 2;
    return a + b + n;
}

I checked the bytecode and they are not the same for these 2 methods. Decompiled code in idea looks like this:

private static String doStuffFinal() {
    String a = "A";
    String b = "B";
    int n = 2;
    return "AB2";
}

private static String doStuffNotFinal() {
    String a = "A";
    String b = "B";
    int n = 2;
    return a + b + n;
}

Why is there a difference between these 2 methods? Can't javac optimize such a trivial case? The compiler could see that a, b and n don't change in doStuffNotFinal and optimize the code in the same way. Why doesn't that happen?

More importantly, does that mean we'd better put the final keyword all over the place just to be sure to get the best optimizations?

like image 205
julian_CT2K2 Avatar asked Mar 02 '23 07:03

julian_CT2K2


1 Answers

Why is there a difference between these 2 methods?

a, b and n are constant variables in the doStuffFinal() method, because:

A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.29)

But the variables in doStuffNotFinal aren't constant variables, because they're not final, and as such their values aren't constant expressions.

As described in Constant expressions, the result of a binary operator with constant expression operands is also a constant expression; so a + b is a constant expression, and so is a + b + n. And also:

Constant expressions of type String are always "interned"

Therefore, a + b + n is interned, and so will appear in the constant pool, thus you will see its use when you decompile.


Can't javac optimize such a trivial case?

The language spec says that the constant string has to be interned in the final case; it doesn't say it can't be in the non-final case. So, sure, it could.

There's only so much time in the day; compiler implementors can only do so much. Such a trivial case is probably uninteresting to optimize for in the compiler because it's going to be pretty rare.


does that mean we'd better put the final keyword all over the place

Don't forget that javac isn't the only thing that does optimization: javac is actually pretty dumb, and literal in its translation of the Java code to bytecode. Far more interesting optimizations occur in the JIT.

Additionally, you only get this benefit of making things final in very specific cases: final String or primitive variables initialized with constant expressions. It of course depends on your codebase, but these are not going to account for a substantial portion of your variables.

So, of course you can spray them everywhere, but it's unlikely to deliver benefits that outweigh the additional visual noise of having final smattered across your code.

like image 126
Andy Turner Avatar answered Mar 05 '23 15:03

Andy Turner