Java-Gaming.org    
Featured games (91)
games approved by the League of Dukes
Games in Showcase (577)
games submitted by our members
Games in WIP (498)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  final modifier  (Read 5531 times)
0 Members and 1 Guest are viewing this topic.
Offline lhkbob

JGO Knight


Medals: 32



« Posted 2007-11-24 19:16:46 »

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.

Online Riven
« League of Dukes »

JGO Overlord


Medals: 605
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #1 - Posted 2007-11-24 20:02:04 »

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

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline erikd

JGO Ninja


Medals: 15
Projects: 4
Exp: 14 years


Maximumisness


« Reply #2 - Posted 2007-11-25 00:25:45 »

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.

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.

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline moogie

JGO Knight


Medals: 11
Projects: 5
Exp: 10 years


Java games rock!


« Reply #3 - Posted 2007-11-25 04:44:48 »

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
Offline CaptainJester

JGO Knight


Medals: 12
Projects: 2


Make it work; make it better.


« Reply #4 - Posted 2007-11-26 03:00:51 »

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 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.

Offline lhkbob

JGO Knight


Medals: 32



« Reply #5 - Posted 2007-11-26 05:09:00 »

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

Offline erikd

JGO Ninja


Medals: 15
Projects: 4
Exp: 14 years


Maximumisness


« Reply #6 - Posted 2007-11-26 11:45:52 »

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.

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?

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.

Online Riven
« League of Dukes »

JGO Overlord


Medals: 605
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #7 - Posted 2007-11-28 18:53:55 »

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

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline erikd

JGO Ninja


Medals: 15
Projects: 4
Exp: 14 years


Maximumisness


« Reply #8 - Posted 2007-11-29 11:32:40 »

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?  Huh

Online Riven
« League of Dukes »

JGO Overlord


Medals: 605
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #9 - Posted 2007-11-29 11:37:17 »

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??

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline erikd

JGO Ninja


Medals: 15
Projects: 4
Exp: 14 years


Maximumisness


« Reply #10 - Posted 2007-11-29 13:48:22 »

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' Smiley nothing more nothing less.
The java platform is too tied to the java language to make a distinction like that imho.

Offline Abuse

JGO Coder


Medals: 10


falling into the abyss of reality


« Reply #11 - Posted 2007-11-29 14:15:49 »

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.

Make Elite IV:Dangerous happen! Pledge your backing at KICKSTARTER here! https://dl.dropbox.com/u/54785909/EliteIVsmaller.png
Offline erikd

JGO Ninja


Medals: 15
Projects: 4
Exp: 14 years


Maximumisness


« Reply #12 - Posted 2007-11-29 15:09:40 »

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...

Online SimonH
« Reply #13 - Posted 2007-11-29 17:39:22 »

Just as an experiment I tried;

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
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;

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
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!


People make games and games make people
Offline lhkbob

JGO Knight


Medals: 32



« Reply #14 - Posted 2007-11-29 18:52:03 »

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

Offline princec

JGO Kernel


Medals: 282
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #15 - Posted 2007-11-30 02:37:26 »

I wonder what the machine code emitted looks like here?

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

Cas Smiley

Online SimonH
« Reply #16 - Posted 2007-11-30 03:25:07 »

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?  Roll Eyes

People make games and games make people
Offline lhkbob

JGO Knight


Medals: 32



« Reply #17 - Posted 2007-11-30 06:27:46 »

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

Offline erikd

JGO Ninja


Medals: 15
Projects: 4
Exp: 14 years


Maximumisness


« Reply #18 - Posted 2007-11-30 10:38:12 »

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.

Offline oNyx

JGO Coder


Medals: 1


pixels! :x


« Reply #19 - Posted 2007-11-30 13:40:18 »

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?  Roll Eyes

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:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
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:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
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

弾幕 ☆ @mahonnaiseblog
Offline erikd

JGO Ninja


Medals: 15
Projects: 4
Exp: 14 years


Maximumisness


« Reply #20 - Posted 2007-11-30 18:23:29 »

Quote
Urban performance legend #2: Declaring classes or methods final makes them faster

I discussed this myth in October's column (see Resources), so I won't rehash it in great detail here. Many articles have recommended making classes or methods final, because it makes it easier for the compiler to inline them and therefore should result in better performance. It's a nice theory. Too bad it's not true.

This myth is even more interesting than the synchronization myth, because there's no data to support it -- it just seems plausible (at least the synchronization myth has a flawed microbenchmark to support it). Someone must have decided that it must work this way, told the story with confidence, and once the story got started, it was spread far and wide.

The danger of this myth, just like the synchronization myth, is that it leads developers to compromise good object-oriented design principles for the sake of a nonexistent performance benefit. Whether to make a class final or not is a design decision that should be motivated by an analysis of what the class does, how it will be used and by whom, and whether you can envision ways in which the class might be extended. Making a class final because it is immutable is a good reason to do so; making a complex class final because it hasn't been designed for extension is also a good reason. Making a class final because you read somewhere that it will run faster (even if it were true) is not.
Quoted from http://www.ibm.com/developerworks/java/library/j-jtp04223.html


Quote
Declaring methods or classes as final in the early stages of a project for performance reasons is a bad idea for several reasons. First, early stage design is the wrong time to think about cycle-counting performance optimizations, especially when such decisions can constrain your design the way using final can. Second, the performance benefit gained by declaring a method or class as final is usually zero. And declaring complicated, stateful classes as final discourages object-oriented design and leads to bloated, kitchen-sink classes because they cannot be easily refactored into smaller, more coherent classes.

Like many myths about Java performance, the erroneous belief that declaring classes or methods as final results in better performance is widely held but rarely examined. The argument goes that declaring a method or class as final means that the compiler can inline method calls more aggressively, because it knows that at run time this is definitely the version of the method that's going to be called. But this is simply not true. Just because class X is compiled against final class Y doesn't mean that the same version of class Y will be loaded at run time. So the compiler cannot inline such cross-class method calls safely, final or not. Only if a method is private can the compiler inline it freely, and in that case, the final keyword would be redundant.

On the other hand, the run-time environment and JIT compiler have more information about what classes are actually loaded, and can make much better optimization decisions than the compiler can. If the run-time environment knows that no classes are loaded that extend Y, then it can safely inline calls to methods of Y, regardless of whether Y is final (as long as it can invalidate such JIT-compiled code if a subclass of Y is later loaded). So the reality is that while final might be a useful hint to a dumb run-time optimizer that doesn't perform any global dependency analysis, its use doesn't actually enable very many compile-time optimizations, and is not needed by a smart JIT to perform run-time optimizations.
Quoted rom http://www.ibm.com/developerworks/java/library/j-jtp1029.html

Offline Linuxhippy

Senior Member


Medals: 1


Java games rock!


« Reply #21 - Posted 2007-11-30 21:58:02 »

Quote
1  
2  
   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

This is not really useful - both methods are static and therefor the "jumped-on" method is exactly the same all the time anyway.
In hotspot it should make a really small difference for non-static functions (uncommon trap check which checks wether the optimization is still valid), for ugly JVMs as well as static compilers it will help more (e.g. J2ME handsets).

I usually use Proguard+Optimization where it counts, it does such things automatically without wasting developer resources or destroying code Smiley

lg Clemens
Offline abies

Senior Member





« Reply #22 - Posted 2008-01-27 20:44:05 »

SimonH - never, ever do benchmarking on single execution of the method. Run same code multiple times and start looking at results only after 10 seconds or more.

As far as final is concerned, there is one major difference for the statics. Javac will inline final statics, while non-final statics will have to be loaded from memory each time.

In below code, in static final case, javac is smart enough to optimize if/throw part completely. I have no idea why, but under server compiler, non-final static version is considerably faster, even with it's load and extra check... On the other hand, on client compiler, static final is almost twice faster before warmup and same speed after some time. It seems that in both cases, final compilation is making static final case a lot worse - could be interesting to see why.

So, there is for sure difference, in pathological cases it might be noticeable difference, but I would not bet on which version is faster - it probably all depends on code alignment in particular method or something similarly obscure.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
public class Test {

   final static int FLOOP = 1000000000;
   static int SLOOP = FLOOP;
   
   
   public static void main(String[] args) {
      for ( int i =0; i < 10; i++ ) {
         long start = System.currentTimeMillis();
         double d = testStatic();
         if ( d > 0 ) {
            System.out.println("Static " + (System.currentTimeMillis()-start));
         }
         start = System.currentTimeMillis();
         double f = testFinalStatic();
         if ( f > 0 ) {
            System.out.println("Static final " + (System.currentTimeMillis()-start));
         }
      }
   }
   
   
   public static double testStatic() {
      double val = 0;
      for ( int i =0; i < SLOOP; i++ ) {
         val += i;
         if ( SLOOP > SLOOP+SLOOP) {
            throw new IllegalStateException();
         }
      }
      return val;
   }
   
   public static double testFinalStatic() {
      double val = 0;
      for ( int i =0; i < FLOOP; i++ ) {
         val += i;
         if ( FLOOP > FLOOP + FLOOP) {
            throw new IllegalStateException();
         }
      }
      return val;
   }
   
}




and disassembly to show that at least in bytecode, static final case should be a lot faster:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
public static double testStatic()
    {
        double val = 0.0D;
    //    0    0:dconst_0        
   //    1    1:dstore_0        
       for(int i = 0; i < SLOOP; i++)
    //*   2    2:iconst_0        
   //*   3    3:istore_2        
   //*   4    4:goto            36
       {
            val += i;
    //    5    7:dload_0        
   //    6    8:iload_2        
   //    7    9:i2d            
   //    8   10:dadd            
   //    9   11:dstore_0        
           if(SLOOP > SLOOP + SLOOP)
    //*  10   12:getstatic       #13  <Field int SLOOP>
   //*  11   15:getstatic       #13  <Field int SLOOP>
   //*  12   18:getstatic       #13  <Field int SLOOP>
   //*  13   21:iadd            
   //*  14   22:icmple          33
               throw new IllegalStateException();
    //   15   25:new             #72  <Class IllegalStateException>
   //   16   28:dup            
   //   17   29:invokespecial   #74  <Method void IllegalStateException()>
   //   18   32:athrow          
       }

    //   19   33:iinc            2  1
   //   20   36:iload_2        
   //   21   37:getstatic       #13  <Field int SLOOP>
   //   22   40:icmplt          7
       return val;
    //   23   43:dload_0        
   //   24   44:dreturn        
   }

    public static double testFinalStatic()
    {
        double val = 0.0D;
    //    0    0:dconst_0        
   //    1    1:dstore_0        
       for(int i = 0; i < 0x3b9aca00; i++)
    //*   2    2:iconst_0        
   //*   3    3:istore_2        
   //*   4    4:goto            15
           val += i;
    //    5    7:dload_0        
   //    6    8:iload_2        
   //    7    9:i2d            
   //    8   10:dadd            
   //    9   11:dstore_0        

    //   10   12:iinc            2  1
   //   11   15:iload_2        
   //   12   16:ldc1            #8   <Int 0x3b9aca00>
   //   13   18:icmplt          7
       return val;
    //   14   21:dload_0        
   //   15   22:dreturn        
   }


Artur Biesiadowski
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

xsi3rr4x (23 views)
2014-04-15 18:08:23

BurntPizza (18 views)
2014-04-15 03:46:01

UprightPath (32 views)
2014-04-14 17:39:50

UprightPath (16 views)
2014-04-14 17:35:47

Porlus (32 views)
2014-04-14 15:48:38

tom_mai78101 (58 views)
2014-04-10 04:04:31

BurntPizza (116 views)
2014-04-08 23:06:04

tom_mai78101 (216 views)
2014-04-05 13:34:39

trollwarrior1 (183 views)
2014-04-04 12:06:45

CJLetsGame (190 views)
2014-04-01 02:16:10
List of Learning Resources
by Longarmx
2014-04-08 03:14:44

Good Examples
by matheus23
2014-04-05 13:51:37

Good Examples
by Grunnt
2014-04-03 15:48:46

Good Examples
by Grunnt
2014-04-03 15:48:37

Good Examples
by matheus23
2014-04-01 18:40:51

Good Examples
by matheus23
2014-04-01 18:40:34

Anonymous/Local/Inner class gotchas
by Roquen
2014-03-11 15:22:30

Anonymous/Local/Inner class gotchas
by Roquen
2014-03-11 15:05:20
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!