Default Arguments
I’ve talked about var args, the arguments keyword, and even the length of a function that has default arguments, but I’ve never written an article all about default arguments… until today.
As a refresher, default arguments look like this:
function foo(a:int, b:int=3): void { }
In the above example, a
is a required argument and b
is a default argument since it has a default value. You can call foo
with either one or two arguments. If you pass one argument, the value of b
will be 3. You can choose any value to be the default so long as it is a compile-time constant, such as a literal or a const
variable. Now, let’s take a look at the bytecode from nemo440:
function private::foo(int,int):void /* disp_id 0*/ { // local_count=3 max_scope=1 max_stack=1 code_len=3 0 getlocal0 1 pushscope 2 returnvoid }
As you can see, nemo440 does not mention anything about either of foo
‘s arguments having a default or being required. Is it possible that default arguments are purely syntax sugar? Let’s look at some calls to foo
:
foo(100, 200); foo(300);
These generate the following bytecode, again courtesy of nemo440 (and commented by me):
30 getlocal0 // get "this" 31 pushbyte 100 // push 100 as first arg 33 pushshort 200 // push 200 as second arg 36 callpropvoid private::foo (2) // call "foo" with two args 39 getlocal0 // get "this" 40 pushshort 300 // push 300 as first arg 43 callpropvoid private::foo (1) // call "foo" with one arg
We see here that foo
is called in two ways. The first way involves passing two parameters and the second passing only one, which matches with how we wrote the AS3 code. Since we don’t see bytecode filling in a pushshort 3
for the second parameter of the call using only one parameter, there must be more going on behind the scenes. To find out what that is, let’s look at the far more robust bytecode output courtesy of Apparat:
Trait Method: Name: AbcQName('foo,AbcNamespace(5,'null)) Disp Id: 0 Is Final: false Is Override: false Method: Return Type: AbcQName('void,AbcNamespace(22,')) Needs Arguments: false Needs Rest: false Needs Activation: false Has Optional Parameters: true Ignore Rest: false Is Native: false DXNS: false Has Parameter Names: false Parameters: Parameter: Type: AbcQName('int,AbcNamespace(22,')) Optional: false Parameter: Type: AbcQName('int,AbcNamespace(22,')) Optional: true Default: 3 Method Body: Max Stack: 1 Locals: 3 InitScopeDepth: 9 MaxScopeDepth: 10 Bytecode: operandStack: 1 scopeStack: 1 localCount: 3 0 exception(s): 3 operation(s): +1|-0 GetLocal(0) +0|-1 PushScope() +0|-0 ReturnVoid()
Apparat gives us access to a treasure trove of information, including the vital “Has Optional Parameters: true” line. Further, it even breaks down the parameters to tell us whether each is required or optional and, if optional, what the default is. Since Apparat doesn’t have access to our AS3 source code, this information must be stored in the SWF file for the Player to use while executing calls to foo
.
Now that we know some more about how default parameters work, let’s see if they impose any performance penalty. Since the Player will be filling in default parameters for us at run time depending on our usage of the function, it’s possible that a speed hit could occur if this is slower than simply pushing all of the parameters on the stack. It’s also possible that this could result in a performance boost if the reverse is true. So take a look at this performance test:
package { import flash.display.*; import flash.events.*; import flash.utils.*; import flash.text.*; public class DefaultParameters extends Sprite { private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } public function DefaultParameters() { __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); const v:int = 33; var beforeTime:int; var afterTime:int; var requiredTime:int; var defaultTime:int; var i:int; const REPS:int = 100000000; log("Num Params,Required,Default"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required1(v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default1(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("1," + requiredTime + "," + defaultTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required2(v,v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default2(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("2," + requiredTime + "," + defaultTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required3(v,v,v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default3(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("3," + requiredTime + "," + defaultTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required4(v,v,v,v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default4(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("4," + requiredTime + "," + defaultTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required5(v,v,v,v,v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default5(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("5," + requiredTime + "," + defaultTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required6(v,v,v,v,v,v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default6(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("6," + requiredTime + "," + defaultTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required7(v,v,v,v,v,v,v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default7(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("7," + requiredTime + "," + defaultTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required8(v,v,v,v,v,v,v,v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default8(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("8," + requiredTime + "," + defaultTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required9(v,v,v,v,v,v,v,v,v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default9(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("9," + requiredTime + "," + defaultTime); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { required10(v,v,v,v,v,v,v,v,v,v); } afterTime = getTimer(); requiredTime = afterTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { default10(); } afterTime = getTimer(); defaultTime = afterTime - beforeTime; log("10," + requiredTime + "," + defaultTime); } private function required1(a:int):void{} private function default1(a:int=1):void{} private function required2(a:int,b:int):void{} private function default2(a:int=1,b:int=2):void{} private function required3(a:int,b:int,c:int):void{} private function default3(a:int=1,b:int=2,c:int=3):void{} private function required4(a:int,b:int,c:int,d:int):void{} private function default4(a:int=1,b:int=2,c:int=3,d:int=4):void{} private function required5(a:int,b:int,c:int,d:int,e:int):void{} private function default5(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5):void{} private function required6(a:int,b:int,c:int,d:int,e:int,f:int):void{} private function default6(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6):void{} private function required7(a:int,b:int,c:int,d:int,e:int,f:int,g:int):void{} private function default7(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6,g:int=7):void{} private function required8(a:int,b:int,c:int,d:int,e:int,f:int,g:int,h:int):void{} private function default8(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6,g:int=7,h:int=8):void{} private function required9(a:int,b:int,c:int,d:int,e:int,f:int,g:int,h:int,i:int):void{} private function default9(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6,g:int=7,h:int=8,i:int=9):void{} private function required10(a:int,b:int,c:int,d:int,e:int,f:int,g:int,h:int,i:int,j:int):void{} private function default10(a:int=1,b:int=2,c:int=3,d:int=4,e:int=5,f:int=6,g:int=7,h:int=8,i:int=9,j:int=10):void{} } }
The above test is long and repetitive so that it can test how default parameters affect performance as you add more and more of them. Here are the results I get when I compile with MXMLC 4.1 in release mode and test on a release version of the Flash Player 10.1 plugin on a 2.4 Ghz Intel Core i5 running Mac OS X 10.6:
Num Params | Required | Default |
---|---|---|
1 | 607 | 639 |
2 | 613 | 654 |
3 | 653 | 688 |
4 | 689 | 732 |
5 | 743 | 792 |
6 | 758 | 898 |
7 | 787 | 1034 |
8 | 848 | 1067 |
9 | 878 | 1101 |
10 | 928 | 1204 |
And here are the above results in graph form:
The above data and graph shows pretty clearly that there is indeed a performance penalty for using default parameters. Let’s take a look at a graph:
We can see that the slowdown in calling time is only about 5% for the first five parameters, but then wildly opens up at the sixth parameter and hovers in the 20-30% range! Thankfully, most functions don’t have anywhere near six default parameters, so this isn’t likely to affect a lot of code. Still, if you do make heavy usage of default parameters (e.g. a 4D Matrix class with defaults for every component), you may want to revisit that code.
#1 by jonathan on January 12th, 2011 ·
Hi, have you try with different parameters in required function call, like required4(v,w,x,y); (with v,w,x and y different constants) ?
#2 by jackson on January 12th, 2011 ·
In the test in the article I set them to 1,2,3,… such that the last test looks like this:
So the parameters are already different constants. Did you have something else in mind?
#3 by jonathan on January 12th, 2011 ·
I talk about “required” not “default” function, you use the same constant “v” for all your calls. Maybe time is preserved with register switching (in assembler level for example) before calling function if the same constant is used.
#4 by jackson on January 12th, 2011 ·
Ah, I see. I switched it from passing
v
for each required parameter to passing 1,2,3… such that the last call looks like this:The results remain relatively the same. Here’s the raw output on a 2.8 Ghz W3530 Intel Xeon with Windows 7:
Both series increase, but the default parameter version increases faster. This matches the Mac results I posted in the article.
Still, it’s good to have further proof that the default parameters are slower than any kind of parameter pushing. Thanks for the idea.
#5 by Andreas Renberg on January 12th, 2011 ·
It’s the “major jump” at 6 parameters which has quite caught my attention. Do you think it’s just a fluke, or is there a reason the performance “leech” appears after 6 params?
Sadly, I don’t know enough about the ActionScript bytecode to look into it myself.
#6 by jackson on January 12th, 2011 ·
It’s hard to tell. Perhaps the Player is optimized for <6 default parameters and then uses generalized, but slower code to handle more. The bytecode looks the same for all versions, but it may be fruitful to look into the Tamarin source.
#7 by kyle_longpeijin on February 4th, 2011 ·
I am a developer from CN, working as a flash mmo game developer for almost a year. I’ve been a reader of your blog for quit a long time, it provides lots of infomations that I really needs.
But when I read this post about the default argument, I have discovered a weird problem about default argument. I don’t know whether you have known about it, so I decided to post it here to see if anyone has any ideas about that.
Here is the case:
Consider there is a class defined as below:
#8 by jackson on February 4th, 2011 ·
I may have seen this problem, but it depends on what the error is. Usually, I get an error saying that
PanelType.FLOAT
is not a constant and that default arguments must be constants. This is a bug in MXMLC. My workaround has been to change the function so that it uses a literal value:Compile and it should work. Then change it back:
Compile and it should work. I often have to do this each time I change the class, but that may have to do with me usually using incremental builds. Next time, I’ll try your strategy.
Am I talking about the same error you are?
#9 by kyle_longpeijin on February 5th, 2011 ·
I think it’s the same error we met, now I see I not the one who’s annoyed by this problem.
Hope it will be solved in the future release of MXMLC.
But still I am quit curious about why changing the method definition back and forth can make the code compile in your strategy. I wonder if there’s any connection between the two compile tries. Could it be posibble that the mxmlc remember what compile error happend last time?
#10 by jackson on February 5th, 2011 ·
Possibly. Like I said, I’m usually using incremental builds (
-incremental=true
), so there’s definitely some connection between compile attempts there. I’m not sure about non-incremental builds, but I never use those outside of one-off tests (e.g. articles for this site).That said, the bug is really annoying sometimes. I hope it’s fixed with MXMLC in Flex Hero (i.e. 4.5).