1

It's been shown (e.g. references in AS3: cast or "as") that it's 3-4x faster to use the "as" keyword versus parenthetic casting in AS3. This is because (cast) could better be described as interpolation, actually generating a new object rather than truly casting the old one. (cast) throws a type error if it fails where the "as" operator returns null. Alright. Three questions here:

(1) What's happening when you pass a Number to a function that expects (int) -- or when you pass a Sprite to a function that expects DisplayObject? i.e. if the function expects a parent class. Is a new, locally scoped object being generated if the expected class is an ascendant of the object you pass as an argument? Is it significantly faster to cast your arguments before calling the function, using "as"?

(2) What's happening in a for each (var i:int in someNumberVector) or a for-each loop treating each Sprite as a DisplayObject, for example? Is every single one re-cast via the slow (not "as" but error-prone) method at each step in the loop?

(3) Does having a function or variable assignment expect an Interface-implementing class (or an Interface itself, e.g. IEventDispatcher) make any difference vs. having it expect a parent class, as far as which method (clone or cast) is used to "cast" the original variable?

Community
  • 1
  • 1
joshstrike
  • 1,753
  • 11
  • 15
  • (1) Number -> int: slow, has to convert. Sprite -> DisplayObject: fast, no work to do. (2) Iterating int vector shouldn't require any extra work (depending on the internal implementation of vector), unless the vector is not declared as a vector of ints, but as a vector of something else, in which cast each iteration requires a conversion. Sprite -> DisplayObject is again instant, because there's nothing to do. (3) Nope. – Cameron Sep 28 '14 at 23:08
  • Okay, just to clarify/expand on this: If I have a dynamically loaded instance -- let's call it var bit:* -- which I know extends Bitmap (for example, the .content of a Loader) and I want to access a Bitmap method on it like getting its bitmapData, I know it's much much faster for me to say (bit as Bitmap).bitmapData than it is to say Bitmap(bit).bitmapData. So my question pertains to what happens if I send this variable as an argument into a function that expects a Bitmap. Which type of casting takes place then? And why would it be compiled differently if we knew 100% the arg extended Bitmap? – joshstrike Sep 29 '14 at 00:05
  • `*` is not a type; it expands to the type of whatever you assign to that variable at compile time (type inference). What happens depends on the type of the variable. This is because if the type extends `Bitmap`, the compiler knows *at compile time* that whatever the variable references at runtime will be a valid `Bitmap` (or null), regardless of whether the actual object is of a subclass or not. If the type is, say, `Object`, then the object cannot just be blindly used as a `Bitmap`, since *at runtime* the variable could reference *any* type of object, not just a `Bitmap`. ... – Cameron Sep 29 '14 at 02:29
  • ... So the compiler has to generate code to do type checking in addition to the cast (the cast itself should be free if the type is indeed `Bitmap` or a derivative). I suggest decompiling your SWF and [looking at the generated bytecode](http://yogda.2ka.org/) if you want to know what's really going on (though that won't tell you *how fast* it's happening, which you should benchmark in your specific circumstances as part of your larger application instead of in a micro-benchmark). I hope all this makes sense :-) – Cameron Sep 29 '14 at 02:29
  • As a general rule of thumb, I would suggest using the actual types (or base types for class objects) whenever possible (i.e. strong typing, where it can be checked at compile time instead of runtime -- this results in fewer bugs and faster code), instead of types like `Object`. Treat subclass-to-base casting as free, and try to avoid all other casts where possible (obviously sometimes it makes sense to cast, or is unavoidable, but there's no reason to be casting everything everywhere when there's perfectly good strong typing available). – Cameron Sep 29 '14 at 02:35

1 Answers1

1

When you pass an object of a subtype (or iterate a list of subtypes, etc.), no casting or conversion is required -- the object is that subtype, and can be passed directly. The reason for this is that the memory layout of objects with inheritance is usually implemented as the base object's data, then the next inheriting class's data, etc. plus a vtable pointer to allow method overriding/interface implementation:

// class Sub : Base
obj -> +----------+
       |vtable ptr| -.      vtable
       |----------|   `-> +---------+
       |Base data |       | Method1 |
       |----------|       +---------+
       |Sub data  |       | Method2 |
       +----------+       +---------+

When passing a Sub object, the underlying pointer points to the start of the object in memory -- which is exactly the same as the Base object's start in memory, so there is nothing to do when converting between Sub to Base internally; the only difference is how the variable is used (type checking semantics). The actual object value, and references to it, need absolutely no conversion (in a sane implementation, anyway).

Adding a cast would only slow things down (unless the compiler is smart enough to remove the cast, which it should be, but I don't have much confidence in the optimization capabilities of the AS3 compiler).

However, casting Numbers to ints and vice versa is completely different, as that requires converting the internal representation of the values -- which is slow.

Note the difference between references to objects (where the variable's actual value is just a handle to that object), and value types (where the variable's actual value is the value itself -- no indirection). For example, an int variable holds the actual integer variable, whereas a variable of type Object merely holds a reference to some object. This means that if you use variables of type Object (or untyped variables which are of Object type by default), and try to stick an integer in them, this will cause what's called a "boxing" operation, which takes the value and sticks it into a temporary object so that the variable can keep a reference to it. Manipulating it like an integer (or explicitly casting it), causes that boxed value to be unboxed, which obviously is not instant.

So you can see, casting objects to their base is not slow, because there's no work to do. Casting a base to a derived type is almost as fast (because nothing has to be done to the internal pointer there either), except that a check has to be done to ensure that the object really is of the derived type (resulting in an exception or null depending on what type of cast was done). Casting value types to object types and vice versa is not very fast, because work has to be done to box/unbox the value in addition to a runtime type check. Finally, casting one basic type to another is particularly slow, because the internal representations are completely different, and it takes correspondingly more effort to do the conversion.

Cameron
  • 96,106
  • 25
  • 196
  • 225
  • Good answer. And it does explain the int/Number issue to some extent. But casting any class to another class via a comparison that could throw a type error (including subclasses to base classes) is provably 3-4x slower than using the "as" operator. So this doesn't really answer whether implicit casting (e.g. when passing a subclass into a function that expects its base class) is being compiled as a simple re-cast or whether it's doing a "Type(var)" and duplicating the object in local scope (thus being much slower). – joshstrike Sep 28 '14 at 23:13
  • @josh: My AS3 is pretty rusty at this point. I was speaking more from a general point of view -- I don't actually know the specific implementation details of the AVM2. But how can casting a class to a base class result in a type error? And did you get that `3-4x` figure from [here](http://jacksondunstan.com/articles/830)? Because there's strong evidence in the comments on that article that the JIT was optimizing out the cast entirely, negating the point of the benchmark. Finally, doing `Type(var)` does not duplicate the object at all -- it really is just a different type of cast. – Cameron Sep 29 '14 at 02:18
  • I did read that Jackson article -- that was one source, another was my own experimentation with casting Number to int which, as you explained, isn't the same thing, but is definitely cast much slower when you do it implicitly in a function call vs bitwise. It seems to me that any method *can* throw a type error even though it never would if you passed in a base class; so it's gotta be doing a findpropstrict/callpropvoid instead of getlex ...and ditto in a for/each loop where that could be very costly. But I'm curious at what point the compiler decides to do that vs the cheaper way. – joshstrike Sep 29 '14 at 02:31
  • @josh: No idea, we're beyond the limits of my knowledge ;-) You'll have to decompile it yourself and see -- but keep in mind the JITter does some optimization too! As I mentioned though, a sane implementation should be able to recognize Derived -> Base conversions are always safe and elide any runtime type checking. – Cameron Sep 29 '14 at 02:37
  • heh, you know, now that you mention it I wasn't even considering how much of this might be different for arguments passed to constructors where everything's interpreted, versus other functions where it can be JIT compiled. I actually now wonder if that isn't the real reason Type(cast) is sometimes slower than "as"...? I'll have to do some tests ;) Thanks! – joshstrike Sep 29 '14 at 03:00
  • @josh: Hmm, yeah that has to be taken into consideration too. But Type(cast) is really only a funny syntax for that kind of cast -- it's not calling the constructor of Type. – Cameron Sep 29 '14 at 13:59