Calling Functions
There are actually three ways to call a function in AS3. Can you name all three? Do you know which is fastest? Does the type of function being called matter? Today I’ll tackle these questions and find some surprising facts about the three ways to call a function in AS3.
The three ways to call a function are:
- The
()
operator—the familiar approach to function calling:foo()
- The
call
method—a way to change thethis
argument:foo.call(thisObj, arg1, arg2, ...)
- The
apply
method—a way to change thethis
argument and pass anArray
of parameters:foo.apply(thisObj, argArray)
The call
and apply
methods provide more advanced function calling features than the ()
operator. While it’s quite rare to need to change the this
argument with call
, it is actually rather common to pass an Array
of parameters with apply
. Also, I should mention that passing the this
argument is optional and it is perfectly fine to pass null
to get the default behavior. Unfortunately, this isn’t mentioned in the documentation for either call or apply.
With that background in mind, take a look at the following performance test:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class CallingFunctions extends Sprite { private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } public function CallingFunctions() { __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); const REPS:int = 10000000; var i:int; var beforeTime:int; var afterTime:int; var functionObject:Function = function(): void {}; var emptyArray:Array = []; log("Function Object"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { functionObject(); } afterTime = getTimer(); log("\t(): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { functionObject.call(null); } afterTime = getTimer(); log("\tcall(null): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { functionObject.call(functionObject); } afterTime = getTimer(); log("\tcall(functionObject): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { functionObject.apply(null, emptyArray); } afterTime = getTimer(); log("\tapply(null): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { functionObject.apply(functionObject, emptyArray); } afterTime = getTimer(); log("\tapply(functionObject): " + (afterTime-beforeTime)); log("Method"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { method(); } afterTime = getTimer(); log("\t(): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { method.call(null); } afterTime = getTimer(); log("\tcall(null): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { method.call(method); } afterTime = getTimer(); log("\tcall(method): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { method.apply(null, emptyArray); } afterTime = getTimer(); log("\tapply(null): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { method.apply(method, emptyArray); } afterTime = getTimer(); log("\tapply(method): " + (afterTime-beforeTime)); log("Static Method"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { staticMethod(); } afterTime = getTimer(); log("\t(): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { staticMethod.call(null); } afterTime = getTimer(); log("\tcall(null): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { staticMethod.call(staticMethod); } afterTime = getTimer(); log("\tcall(method): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { staticMethod.apply(null, emptyArray); } afterTime = getTimer(); log("\tapply(null): " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { staticMethod.apply(staticMethod, emptyArray); } afterTime = getTimer(); log("\tapply(staticMethod): " + (afterTime-beforeTime)); } private function method(): void {} private static function staticMethod(): void {} } }
The above test performs many function calls using all three methods on a local Function
object, a method of the class, and a static method of the class. It also passes null
and the actual this
object to call
and apply
. Here are the results I get on a 2.4 Ghz Intel Core i5 with Mac OS X 10.6 using Flash Player 10.1.102.64: (bold results are fastest for each type of function)
Approach | Function Object | Method | Static Method |
---|---|---|---|
() | 302 | 71 | 79 |
call(null) | 264 | 799 | 821 |
call(this) | 358 | 1213 | 1224 |
apply(null) | 466 | 893 | 882 |
apply(this) | 453 | 1289 | 1279 |
Here are some conclusions we can draw based on the above results table:
- The normal
()
operator is by far the fastest way you can call a method, static or otherwise Function
objects can actually be called marginally faster withcall
than()
- Using
call
orapply
with methods slows them down so much that they are no longer 4x faster thanFunction
objects but rather 2-4x slower thanFunction
objects - Using
call
andapply
withFunction
objects doesn’t affect the calling speed much, either positively or negatively - Passing
null
tocall
is always significantly faster than passing the actualthis
object - Passing
null
toapply
is significantly faster only than passing the actualthis
object with methods. It’s actually slightly slower withFunction
objects
If any of the above points apply to any performance-critical code you have, you might want to revisit those areas. This is especially true if you’re using call
or apply
with methods; I suggest you find a way to convert those function calls to the good old ()
operator!
#1 by makc on November 8th, 2010 ·
Woah, static methods are just as fast to call? This is something new in 10.1
#2 by jackson on November 8th, 2010 ·
This test shows them to be 11% slower in the optimal case: when using the
()
operator. The last time I tested function speed was an update for Flash Player 10.1 and it showed static methods to be 13% slower than non-static methods. That was an update to my article Function Performance where I used the then-current Flash Player 10.0 and showed static methods to be 25% slower than non-static methods. So things are definitely improved in 10.1, but static methods are still slower than non-static methods.#3 by skyboy on November 8th, 2010 ·
When you run global/static methods through the toString function on an Object’s prototype, you get
[object Function-1]
while if you run methods and functions declared within functions you get[object Function-4]
. so they’re not the same type of object internally, which is likely why the slowdown occurs.#4 by makc on November 8th, 2010 ·
hmmm only 25%… I seem to remember a lot more dramatic improvements, I mean away3d changed whole API once just to get rid of static funcs, and I remember very real extra frames per seconds. I don’t have any numbers, though, just memories.
#5 by makc on November 8th, 2010 ·
btw, I assume that the way you call func does not change the speed of its body. or does it? the only case I heared of was code in constructor.
#6 by jackson on November 8th, 2010 ·
I wrote an article showing that constructors aren’t slow. However, if the function you call has a local function in it, you’ll get a slowdown. That’s not the case with these (empty, non-constructor) functions though.
#7 by skyboy on November 8th, 2010 ·
You omitted tests with arguments, I have a feeling that 3-4 arguments (which could also double the tests for apply: dynamic and pre-allocation) would affect the test results for which is slowest/fastest.
#8 by jackson on November 8th, 2010 ·
I did omit these tests, but here are the results for a 2.8 Ghz Intel Xeon W3530 with Windows 7 and Flash Player 10.1:
While the versions with arguments are slower, the relative findings are still the same.
#9 by skyboy on November 8th, 2010 ·
Not quite the same: they all show that the direct call has a far smaller slow down, I’m not that sure on the internal workings of apply (some sort of odd proxy mechanism for call, possibly), however, it looks like there’s a dynamically allocated array in use there, but from those tests, using a direct call instead of the call/apply methods when possible on Function Objects is far better, 60% faster than call.
Oddly, static methods look faster than normal methods when there are multiple arguments. This may be some trickery on adobe’s end used to speed up the static methods of the inbuilt classes to meet a performance quota.
#10 by skyboy on November 8th, 2010 ·
I vote for an edit button on comments.
As an after thought, I realized that storing the methods in a variable before using call/apply in a loop should halve the execution speed.
#11 by jackson on November 8th, 2010 ·
I’ll see what I can do about an edit button for non-admins.
I’d say that
call
andapply
are roughly the same performance-wise, regardless of the number of arguments. The numbers I posted in the comment are just a couple of random tests, so there will be some wiggle with repeat tests. They usually come out pretty close, but sometimesapply
will beatcall
and sometimes it’ll work out the other way around. You’re right about the dynamically-allocatedArray
though:call
takes var args after thethis
object and var args is slow (10.1 update).I think the argument cost is about the same regardless of the call, so as you add more arguments to the test it starts to add so much overhead that the calling approach gets lost in the numbers.
What do you mean by “storing the methods in a variable before using call/apply in a loop should halve the execution speed”? If you mean storing a method as a local
Function
-typed variable, I think you’d drop the performance by a lot more than half. At least the above numbers would seem to show that (~4x in some cases).#12 by skyboy on November 8th, 2010 ·
I had meant on apply, since on average it takes 300ms longer than call. The fidgeting always happens, and sometimes it’s surprisingly drastic; I’ve seen 10x speed boosts in some cases and can’t duplicate them.
#13 by kyle_longpeijin on February 10th, 2011 ·
Did you ever notice that if we use apply() while setting thisObj to null, the “this” pointer in that function cannot work correctly, it’s always a null.
For excample, the following method will throw a null pointer error saying that the “this” pointer is null or someProperty doesn’t exist.
#14 by kyle_longpeijin on February 10th, 2011 ·
The nested callback function that I mean in the last two paragraph is just like the following example:
#15 by jackson on February 10th, 2011 ·
I haven’t experienced this problem before. In fact, I just did this test:
And I see the log message just fine.
Could you provide a complete test AS file demonstrating this behavior? I’d be really interested to know what’s causing this problem.
#16 by skyboy on February 10th, 2011 ·
The functions aren’t methods, but are instead function instances; Instances, unlike methods, can have a variable
this
object.The problem most likely arises from using timeline code instead of classes.
#17 by kyle_longpeijin on February 11th, 2011 ·
Oh, I think I made a little mistake. I was confused by method closure and function object.
The actual case that I mean should look like this:
#18 by jackson on February 11th, 2011 ·
Ah, that clears it up. I’m bitten by this difference very often, but nowadays I know the symptoms of the bug and diagnose the problem pretty quickly. It’s important to do what you did: thoroughly read Adobe’s documentation. One workaround to (possibly) preserve some readability and definitely preserve explicit scoping is to do this:
Of couse, you should replace
MyClass
with the classstart
is in. You might also prefer names other thanthiz
, likethisObj
, orthisRef
.