Amazingly, I’ve never covered the const keyword, but a couple of recent comments have prompted me to cover the subject in depth with today’s article.

There’s much to be excited about with const:

  • Safety– the compiler will give you an error if you don’t initialize your const when declaring it or you assign to it later
  • Performance– a good way to tell the compiler and JIT that it can apply a bunch of optimizations like constant folding

Unfortunately, older versions of MXMLC and Flash Player only enforced const at compile time and did nothing for performance. Has anything changed now that we have MXMLC 4.1 and Flash Player 10.1? To investigate this, consider the following two functions:

private function testVar(): int
{
	var val:int = 3;
	return val + val;
}
 
private function testConst(): int
{
	const val:int = 3;
	return val + val;
}

Here’s how they get compiled by MXMLC 4.1.0.16076 (the latest stable version as of this writing):

  function private::testVar():int	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=2 code_len=9
    0       getlocal0     	
    1       pushscope     	
    2       pushbyte      	3
    4       setlocal1     	
    5       getlocal1     	
    6       getlocal1     	
    7       add           	
    8       returnvalue   	
  }
 
  function private::testConst():int	/* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=2 code_len=9
    0       getlocal0     	
    1       pushscope     	
    2       pushbyte      	3
    4       setlocal1     	
    5       getlocal1     	
    6       getlocal1     	
    7       add           	
    8       returnvalue   	
  }

As you can see, these functions are compiled to exactly the same bytecode. There is no constant folding going on here, at least in bytecode. Nevertheless, let’s see if some intensive usage of a const variable improves performance:

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
 
	public class ConstTest extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function log(msg:*): void { __logger.appendText(msg + "\n"); }
 
		public function ConstTest()
		{
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			var i:int;
			var beforeTime:int;
			var afterTime:int;
			const REPS_CONST:int = 100000000;
			var REPS:int = REPS_CONST;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
			}
			afterTime = getTimer();
			log("var: " + (afterTime-beforeTime));
 
			REPS = REPS_CONST;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS_CONST; ++i)
			{
			}
			afterTime = getTimer();
			log("const: " + (afterTime-beforeTime));
		}
	}
}

Each loop is compiled to the same bytecode:

    95      pushbyte      	0
    97      setlocal1     	
    98      jump          	L3
 
    L4: 
    102     label         	
    103     inclocal_i    	1
 
    L3: 
    105     getlocal1     	
    106     getlocal      	4
    108     iflt          	L4

I’ve thrown in a re-assignment of the REPS variable to make the JIT’s optimization job harder. This test is about half var and half const. While that will cut const’s supposed performance advantages in half, they should still be apparent with enough iterations. Let’s try 100 million with Flash Player 10.1.85.3:

Environment Var Const
2.4 Ghz Intel Core i5, Mac OS X 215 215

This shows that const and var run at the same speed. The JIT has therefore not optimized const. So, if const can’t improve performance, can compile-time constants?. Consider the following addition:

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
 
	public class ConstTest2 extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function log(msg:*): void { __logger.appendText(msg + "\n"); }
 
		public function ConstTest2()
		{
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			var i:int;
			var beforeTime:int;
			var afterTime:int;
			const REPS_CONST:int = TEST::REPS_DEFINE;
			var REPS:int = REPS_CONST;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
			}
			afterTime = getTimer();
			log("var: " + (afterTime-beforeTime));
 
			REPS = REPS_CONST;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS_CONST; ++i)
			{
			}
			afterTime = getTimer();
			log("const: " + (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < TEST::REPS_DEFINE; ++i)
			{
			}
			afterTime = getTimer();
			log("define: " + (afterTime-beforeTime));
		}
	}
}

This is compiled with the same number of loop repetitions:

mxmlc -define=TEST::REPS_DEFINE,100000000 ConstTest2.as

The loop bytecode for this version looks like this:

    142     pushbyte      	0
    144     setlocal1     	
    145     jump          	L5
 
    L6: 
    149     label         	
    150     inclocal_i    	1
 
    L5: 
    152     getlocal1     	
    153     pushint       	100000000	// 0x5f5e100
    155     iflt          	L6

The only difference is that the getlocal 4 access of the var/const has been replaced by pushint 100000000 pushing the literal value. So we’ve succeeded in eliminating the local variable/constant, but has it helped? Let’s see:

Environment Var Const Define
2.4 Ghz Intel Core i5, Mac OS X 215 215 241

Pushing the constant is 12% more expensive than accessing the local variable or constant! So what have we found today?

  • const provides helpful compile-time checking
  • const is not faster than var
  • Using a compile-time constant is slower than a local var or const