final modifier

From everything that I’ve heard and read, the final modifier when used on methods should cause some slight performance gains because of compiler optimizations. However, when I’ve marked a few methods in a rendering engine I’m working on as final, it causes a performance hit. The hit isn’t too substantial (about a 2 fps) but it is very consistent so it’s not timer error. The methods marked final are ones that would be called about 10,000 times a frame, so it the actual performance loss per final method call is very, very small.

I’m curious as to why this is happening, since really I should be gaining a few frames a second.

The JIT is an (opensource) blackbox. Any seemingly irrelevant change may change performance one way or another.

In the old days (I’m talking something like 10 years ago), the final modifier could couse a performance gain because it acted as a hint that the JIT could inline. Nowadays, HotSpot has gotten a lot smarter with inlining. Why you’re seeing a performance loss, I’m not sure, but as a rule of thumb; just don’t use the ‘final’ modifier for anything other than a design choice.

I have made heavy use of the final modifier in my raytracer engine… it has not really made any noticable difference in execution speed, however it dose allow optimisers (such as proguard) to better compress the classes

I have seen performance gains under 1.5 using final. I have been trying to write an emulator, on and off, for the Commodore 64 in Java. It contains a lot of method calls, I mean a lot. So I made the ones that are called very frequently final and saw the speed at least double.

Perhaps it’s that I’m using os x’s jvm and they haven’t followed the specification?

I have tried it in the 1.4 days with JEmu2, never saw any difference…

[quote]Perhaps it’s that I’m using os x’s jvm and they haven’t followed the specification?
[/quote]
This has nothing to do with following specification. You won’t find anything about the ‘final’ modifier in the JLS suggesting that it should affect performance; it’s all a matter of implementation.
The ‘final’ modifier is a language feature which means that you can’t override or reassign, nothing more nothing less. Nothing to do with performance.

It actually is more than a language feature, as it retained in the bytecode.

Of course it’s in the byte code, but why does that make it more than a language feature? ???

Generics are a language-feature. They vanish at compile-time.

final is also relevant in the JVM implementation. So it’s not only the language (the sourcecode), it’s the platform (sourcecode+bytecode).

Maybe my definition of ‘language feature’ is wrong??

I dunno, maybe my definition is wrong.
But I don’t see a reason why language features should vanish at compile time; for me a language feature is just a… well… ‘feature of a language’ :slight_smile: nothing more nothing less.
The java platform is too tied to the java language to make a distinction like that imho.

There has always been a clear distinction between the Java language, and the Java bytecode (& virtual machine)

Language specification:
http://72.5.124.55/docs/books/jls/third_edition/html/j3TOC.html

VM specification:
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html

While final is a keyword in the Java language that is applicable to:
variables: http://72.5.124.55/docs/books/jls/third_edition/html/typesValues.html#4.12.4
classes: http://72.5.124.55/docs/books/jls/third_edition/html/classes.html#8.1.1.2
fields: http://72.5.124.55/docs/books/jls/third_edition/html/classes.html#8.3.1.2
and methods: http://72.5.124.55/docs/books/jls/third_edition/html/classes.html#8.4.3.3

final is also independently defined in the VM spec as the ACC_FINAL flag.
This flag can be set for classes, methods or fields. (the concept of final variables does not exist in the VM spec)

It’s important to remember the set of programs expressible through the Java language is a subset of the programs expressible through Java byte code.

True.
I guess I was referring to whether or not the ‘final’ keyword can be called a ‘language feature’ just because the concept also happens to be known in java byte code. IMHO by far most java language features are (have to be) supported by java byte code, but maybe my interpretation of the word ‘feature’ is wrong I dunno…

Just as an experiment I tried;

class FinalTest
{
	static int number=1;
	static final int FINAL_NUMBER=1;

	public static void main(String[] args)
	{
		long a;
		long startTime,endTime;
		long count=1000000000;

		a=0;
		startTime=System.currentTimeMillis();
		for (long i=0;i<count;i++) {a+=number;}
		endTime=System.currentTimeMillis();
		System.out.println(""+count+" adds int took "+(endTime-startTime)+" ms");

		a=0;
		startTime=System.currentTimeMillis();
		for (long i=0;i<count;i++){a+=FINAL_NUMBER;}
		endTime=System.currentTimeMillis();
		System.out.println(""+count+" adds final int took "+(endTime-startTime)+" ms");
	}
}

Results for java version 1.6.0_03;
1000000000 adds int took 5547 ms
1000000000 adds final int took 2968 ms

Just under twice as fast - pretty much what I’d expected.

Then I tried;

class FinalTest
{
	public static void main(String[] args)
	{
		long a;
		long startTime,endTime;
		long count=1000000000;

		a=0;
		startTime=System.currentTimeMillis();
		for (long i=0;i<count;i++){a=addInt(a,1);}
		endTime=System.currentTimeMillis();
		System.out.println(""+count+" adds int took "+(endTime-startTime)+" ms");

		a=0;
		startTime=System.currentTimeMillis();
		for (long i=0;i<count;i++){a=addFinal(a,1);}
		endTime=System.currentTimeMillis();
		System.out.println(""+count+" adds final int took "+(endTime-startTime)+" ms");
	}

	private static long addInt(long a, int b){return a+b;}
	private final static long addFinal(long a, int b){return a+b;}
}

and got;
1000000000 adds int took 2953 ms
1000000000 adds final int took 3047 ms

Very slightly slower!

Your final method results being slightly slower is the exactly what I was experiencing although I never bothered to compress it into a test case to see exactly how much it affected things. Thanks

I wonder what the machine code emitted looks like here?

I would expect Hotspot to emit the same code for both loops.

Cas :slight_smile:

Whoa!

I just took my second example and reversed the call order of the methods (ie final first) and got;

1000000000 adds final int took 2859 ms
1000000000 adds int took 2938 ms

Which makes final faster! Hmmm - is this a good test? ::slight_smile:

Maybe try not having the adding methods private. I thought that private methods were automatically considered final since they couldn’t be overridden anyway. Public methods might give a better test case, although it’s interesting that including the final did make a difference

I wouldn’t make too many conclusions based on that test case. A difference of 2859 vs 2938 ms, is just about noise level, and might not have anything to do with the final keyword. For example there’s no VM warm-up in this benchmark.

We saw the same effect in the collections thread. It’s better to use some command line switch and only run a single test per run.

Eg:

import java.util.*;
public class StringCatBench{
	final static int ITERATIONS=100000;
	final static int RUNS=100;
	final static int REPEAT=10;
	public static void main(String[]args){
		if(args.length!=1)
			printUsageAndDie();
		if(args[0].equals("buffer"))
			testBuffer();
		else if(args[0].equals("builder"))
			testBuilder();
		else
			printUsageAndDie();
	}
	private static void printUsageAndDie(){
		System.out.println("usage java StringCatBench <buffer|builder>");
		System.exit(1);
	}
	private static void testBuffer(){
		for(int re=0;re<REPEAT;re++){
			long start=System.currentTimeMillis();
			for(int r=0;r<RUNS;r++){
				StringBuffer sb=new StringBuffer();
				for(int i=0;i<ITERATIONS;i++)
					sb.append("x");
				String s=sb.toString();
			}
			long end=System.currentTimeMillis();
			System.out.println(end-start);
		}
	}
	private static void testBuilder(){
		for(int re=0;re<REPEAT;re++){
			long start=System.currentTimeMillis();
			for(int r=0;r<RUNS;r++){
				StringBuilder sb=new StringBuilder();
				for(int i=0;i<ITERATIONS;i++)
					sb.append("x");
				String s=sb.toString();
			}
			long end=System.currentTimeMillis();
			System.out.println(end-start);
		}
	}
}

Output:

X:\>java StringCatBench buffer
656
641
641
640
625
641
640
610
531 <- gets faster after the 8th repetition
516

X:\>java StringCatBench builder
468
469
469
453
469
468
454
468
469
453