12.13.2009

Closure on Closures, Terrible Typing, For-Each Failures

Here's an old reply I wrote to another blog's wonderings on performance problems in Flash functions, numeric types, and array access. I'm not in the mood to review, rewrite, nor make corrections (e.g. I'm sure I used "function closure" where I meant "activation object" and vice-versa, but not important), but here's the text of my replies (not entirely self-contained, but I recommend reading the original post either way):

From http://robert.zubek.net/blog/2008/01/12/actionscript-3-bytecode-performance/#comment-258
Anonymous functions/closures have tricky semantics in AS3, which is why you saw such painful numbers.

AS3 is not block-scoped, so all variable definitions are hoisted to the top-level of their scope — in this case, the function body of the surrounding function, not the anonymous function. This is why you cannot have two for loops with the same ‘var i:Number’ definition — you would get a ‘Duplicate name i’ error.

So, when you introduce an anonymous function, the cost of *every* variable (within the outer function down to the deepest variable definition) is increased considerably. You have to do a more expensive lookup. This is why the Flex SDK doesn’t use them, ever :)

Sometimes, you *need* a closure for an event listener… My solution is to create a simple factory function that takes the values you want to close over, and returns a Function object. This doesn’t get around the cost of creating a closure, and it adds the cost of the factory function call, but it isolates the lookup costs to the factory function.

In small functions, it may be more expensive to use this technique, but I never tested it.



As for for-each loops: The special form returns values from an array in unspecified order because the backing object is a hashtable (it sometimes begins life as a dense array, but often becomes a hashtable). Assuming that your for(;;) test was iterating over indexes from 0..len-1, each iteration does a hash lookup. We’d like lookup to be O(1) amortized, but there is considerable hidden overhead in doing the lookup.

The hash function is a quadratic probe, and we have a high load factor (~80%), so a large array has a high probability of collision. To calculate the address, integers indexes must be cast to Number (not terrible, but this is why the Flex SDK uses Number instead of Integer). If you index on a string or the cast fails, we stringify the index, intern it, and use that (!).

You *might* be able to tell if you have a dense array if the ‘for each’ loop returns items in ascending order, but that’s just a guess. You could try it with a 10 element array versus a 1000 element array, perhaps.

Player 10 should improve some of the casting costs and differences in performance between numerical types.



Another gotcha. Though no professional uses them, ‘with’ blocks introduce the same overhead that anonymous functions do, without any of usefulness. [Ed: It was pointed out to me that it's super-useful in graphics code -- true! though given the number of instructions you're likely to give for any non-trivial graphic, you're best off making a variable named g and being explicit about it.] You push the with object onto the scope chain, and then have to search the scope chain for every call within that block.

Too bad we didn’t implement them the way VB did, where you could tell which statements used the with object because they all started with a “.” (of course, there would be limitations).

Many equivalent expressions in AS3 are not equivalent in byte size or performance. Many of the convenience mechanisms, like with, are useless because their performance is so poor. I always meant to explore this and actually write posts in my blog, flexamphetamine.

By the way: Have you tested different kinds of variable accesses? I forget what my results were (two years ago…), but this might have been the order of expense (from least): argument variables (strange, huh?), local variables, class variables. I don’t think I tested statics.

3.26.2009

Alphas Go to Eleven

In case you've always wondered why setting alpha to 0.9 doesn't return 0.9 when you read it back...

Internally, alpha is stored as an integer between 0 and 255, and auto-rounded/converted from the input of [0, 1.0]. This is braindumb design, but a fact of life.

So if you add 0.1 ten times to an alpha of 0, you get a number less than 1.0, and need to go to eleven (going to eleven is usually a good thing, but in this case it isn't).

If you are incrementing the alpha by 0.01, the actual alpha will be off more than 20% by the 100th iteration (!).

So the only way to keep alphas honest is to store the last value you tried to set alpha to, and use that as the input in your code.

Moral: Don't use the existing value of alpha (ever), always set it and manipulate it from a model.

2.08.2009

Monkeypatching the cached Flex framework RSL

I believe I discovered a *consistent* method to monkeypatch the Flex framework with RSLs. I assume you understand what a preloader is, and how to monkeypatch without RSLs -- there is plenty of documentation on both.

Like the first time you type sudo:
We trust you have received the usual lecture from the local former Flex SDK dev. It usually boils down to these three things:
#1) Respect the privacy of other classes.
#2) Think before you patch.
#3) With great power comes great responsibility.
Create an empty preloader class (or add to an existing one) which contains the following, substituting for whatever you're patching.
    import mx.managers.FocusManager; FocusManager;
And that'll do it. The class will get preloaded, before the cached framework gets loaded. Simple and obvious now, eh?

---
Note: If using RSLs, you may see significant code bloat. Classes get loaded in order of dependency -- if the dependency of UIComponent isn't present when it tries to load, it fails.

So if you are patching high up in the dependency tree, like UIComponent, all the required classes will get compiled into the main SWF, as they are loaded before the RSLs.

Notably, this is unlike how Java's class loader works.

1.08.2009

Heisenberg on Garbage Collection

Detecting if a reference gets garbage collected is tricky. In general, you cannot do this because of the Heisenberg Uncertainty Principle -- if you hold a reference, then it cannot get GCd; if you don't hold it, then you cannot check if it got GCd. Until now :)

Example (try uncommenting the line which nulls our important reference):

// the object we're curious about
var impReference:Object = new Object();
public function init():void
{
// this code monitors an object for GC, giving it a name.
monitorGC("impReference", impReference);
//impReference = null; // try uncommenting me

// GC rarely occurs until after code runs, after a frame or so
callLater(function():void
{
// call twice USUALLY makes a GC occur, as does opening
// a LocalConnection to the same name twice in a row
System.gc();
System.gc();
// look at the error console
if (checkIfGCd("impReference"))
trace(name + " got GCd")
});
}

// support code
static public function monitorGC(name:String, reference:*):void
{
// this is how we get around Heisenberg, a strong Dictionary holding
// weak dictionaries, each holding a single reference, or no references
(gcReferences[name] = new Dictionary(true))[reference] = true;
}

static public function checkIfGCd(name:String):Boolean
{
var gotGCd:Boolean = true;
for (var foo:* in gcReferences[name])
gotGCd = false;
return gotGCd;
}

static private const gcReferences:Dictionary = new Dictionary();