Win a copy of Murach's Python Programming this week in the Jython/Python forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

Making everything the compiler allows to be final, final  RSS feed

 
Tom Hughes
Ranch Hand
Posts: 86
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Would this make programs run faster ?
If so, are there any tools to precompile java code in that way ?
Tom.
 
Jim Yingst
Wanderer
Sheriff
Posts: 18671
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Well, it might make programs run a bit faster, as it allows the compiler and JVM to perform additional optimizations. However I'm guessing that the effect will be inconsequential at least 98% of the time, and could lead to other complications. E.g. if you make methods final just because you can, you rule out the possibility of overriding them later. This will most likely cause more headaches than it solves. (Fields and local variables, on the other hand, are somewhat easier to redefine later if necessary.) I recommend that you choose whether to use final on a case-by-case basis, based on overall design considerations (should this method/field/class be final?) and ignoring performance issues. If and when performance is a problem, use a profiler to determine which sections of your code are really the problem. Than and only then, you might experiment to see if making a few critical elements final has any significant effect. But don't forget whatever design considerations led you to not make the element final in the first place - it may still be important.
[ July 23, 2002: Message edited by: Jim Yingst ]
 
SJ Adnams
Ranch Hand
Posts: 925
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Has anyone done any measurements on this?
I've got a feeling that it may be better than 98%
Cheers, Simon
 
Ilja Preuss
author
Sheriff
Posts: 14112
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
In early Java implementations, declaring a method final could have noticable effect on performance, as the compiler was able to inline such methods which couldn't be done with non-final methods because of polymorphism.
As the modern HotSpot engine is capable of inlining performance critical methods at runtime even when they are not final, their really isn't much need for that form of optimization any more.
And, of course, their is still the First Rule Of Optimization: Don't!
 
Mark Herschberg
Sheriff
Posts: 6037
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have been told that it does make a difference, because the JVM doesn't have to worry about subclassing. Obfuscators can do this easily, and it makes no change to the source code.
--Mark
 
SJ Adnams
Ranch Hand
Posts: 925
 
SJ Adnams
Ranch Hand
Posts: 925
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
This is following up the disussion in the SCEA forum. I added a derived method to the experiment...
public class C{
static int i;
private static void a(){
i++;
}
public void b(){
i++;
}

public final void c(){
i++;
}
public static void main(String[] args) throws Exception{
C c = new C();
D d = new D();
long l = System.currentTimeMillis();
for(int i=0;i<100000;i++){
for (int j=0;j<10000;j++){
a();
}
}
System.out.println("static method :" + (System.currentTimeMillis() - l));
l = System.currentTimeMillis();
for(int i=0;i<100000;i++)
for (int j=0;j<10000;j++){
c.b();
}
System.out.println("non final method :" +(System.currentTimeMillis() - l));
l = System.currentTimeMillis();
for(int i=0;i<100000;i++)
for (int j=0;j<10000;j++){
c.c();
}
System.out.println("final method :" +(System.currentTimeMillis() - l));
l = System.currentTimeMillis();
for(int i=0;i<100000;i++)
for (int j=0;j<10000;j++){
d.b();
}
System.out.println("derived method :"+ (System.currentTimeMillis() - l));

}
}
class D extends C {
public void b(){
i++;
}
}
The results with JDK 1.3 were

C:\>java C
static method :4984
non final method :11016
final method :4281
derived method :10782
As you can see the derived method takes just as long as the non final method. However a rebuild with JDK 1.4.....
C:\>java C
static method :5094
non final method :10906
final method :4000
derived method :4313
And it looks like Sun has figured out it can treat the 'non final' derived method as final.
We conclude:
JDK 1.3 adding final will improve speed.
JDK 1.4 very small advantage, not worth altering code.
HTH, Simon
P:\>java -version
java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0
Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)
 
Tom Hughes
Ranch Hand
Posts: 86
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Ilja Preuss:

And, of course, their is still the First Rule Of Optimization: Don't!

My original thought was that it could be done by a precompiler (i.e. so it wouldn't affect your source code).
This would be an optimization no-brainer. This could be used if you actually had a performance problem or alternatively just to make your system that bit nippier or your GUI that bit more responsive (who woouldn't want this when using Swing).
what do you reckon ?
Tom
 
Ilja Preuss
author
Sheriff
Posts: 14112
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Tom Hughes:
My original thought was that it could be done by a precompiler (i.e. so it wouldn't affect your source code).

That rather well describes what the Hotspot Engine of the JDK1.4 *is* doing...

This would be an optimization no-brainer.

Well, not exactly, as with Javas capability of loading additional classes at runtime, you can't exactly know which method might need polymorphism later on. So Hotspot must be able to replace an optimized method call with the original one if necessary.
 
Tom Hughes
Ranch Hand
Posts: 86
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
True, I'm not peddling this as an idea that will be useful for everyone but as a mechanism that ill be usefull.
Originally posted by Ilja Preuss:

Well, not exactly, as with Javas capability of loading additional classes at runtime.

I never do this. If I change my programs I always restart them. I'm guessing that a high percentage of program changes happen this way.
Originally posted by Ilja Preuss:

That rather well describes what the Hotspot Engine of the JDK1.4 *is* doing...

I'll take your word for it, but it doesn't seem to be doing a particularly good job of it
Originally posted by Simon Lee :

As you can see the derived method takes just as long as the non final method. However a rebuild with JDK 1.4.....
C:\>java C
static method :5094
non final method :10906
final method :4000
derived method :4313

final method of 4000 vs non final 10906
am I missing something here ?
Tom
 
Ilja Preuss
author
Sheriff
Posts: 14112
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Tom Hughes:
final method of 4000 vs non final 10906
am I missing something here ?

Yes, the non final method is overridden in the class D and therefore *could not* be declared final. You have to compare the final method to what Simon calls the derived method (4313).
 
Thomas Paul
mister krabs
Ranch Hand
Posts: 13974
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Here were my numbers using J2SE 1.4.1:
static method :4609
non final method :31719
final method :3656
derived method :3687
overridden method :31579
I also wanted to see what would happen with running a non-final method making use of polymorphism. That is the last entry there. The code to produce it:

This means that making use of polymorphism is expensive but not any more expensive than running a non-final method of a parent class.
I changed the code to check to see if c was an object of type D and if it was, cast it to a D before running the method. The result was: 9094. Removing the instanceof check and just going with the cast reduced the performance hit to: 7187.
So it is much cheaper to cast than it is to use polymorphism. No real surprise.
What is surprising is that the JVM "knows" that the derived method isn't overridden. It appears to be inlining the d.b() call. It can only do that if it knows that d must always be pointing to a D. That is why we have the performance hit on the c.b(). The JVM "knows" that c could be a C or a D so it can't inline the call.
Very interesting.
[ September 11, 2002: Message edited by: Thomas Paul ]
 
SJ Adnams
Ranch Hand
Posts: 925
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Very interesting.

Very Cool.
 
Ilja Preuss
author
Sheriff
Posts: 14112
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Thomas Paul:
What is surprising is that the JVM "knows" that the derived method isn't overridden. It appears to be inlining the d.b() call. It can only do that if it knows that d must always be pointing to a D. That is why we have the performance hit on the c.b(). The JVM "knows" that c could be a C or a D so it can't inline the call.
Very interesting.

Yes, thanks for the analysis!
Another interesting testcase would be to extract the part with the call to d.b() into its own method and call it once before and once after a subclass of D has been loaded.
But I won't try that today - it's bedtime in germany...
 
Thomas Paul
mister krabs
Ranch Hand
Posts: 13974
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I did a bunch of tests. These were done by instanitiating a C and a D prior to running the tests. The first four are simply tests of methods in C, a static method, a private method, a non-final public method that is not overridden in D, and a final method. The times are all failry close with the static being the slowest. The JVM was able to inline all of these because it knew that none of them were overridden in D. The next is the method that is overridden in D. This was not inlined. The next is the method in D that overrides the method in C. That was inlined. And last is running a method in D but through a pointer of type C.
static method :4391
private method :3672
public method :3625
final method :3594
non final overridden method :9547
overriding method :3578
polymorphism method :9703
So the JVM is very clever. It knows what classes have children and what methods are overridden. The polymorphism performance punishment seems to exist only when it is used.
It occurred to me that if I moved the instantiation of the D to after the test that used the non-final overridden method that the performance hit should go away. After all, without a D object in the JVM, it should be able to figure out that C can't be overridden. This is true except that there is an odd quirk. Prior to the last test, the code contains the line c = d;. This is for the polymorphism test. As long as that line is in the code, the JVM doesn't inline the method call for the non-final overridden method even if that method call occurs before the c=d statement.
In other words:

runs faster than:

I get these results:
Code block 1:
non final overridden method :3593
Code block 2:
non final overridden method :9657
polymorphism method :9610
[ September 11, 2002: Message edited by: Thomas Paul ]
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!