Hidden Object Allocations
During some recent memory profiling I was reacquainted with just how many ways there are to unknowingly allocate an object in AS3. The problem is seldom the allocation itself, but rather the later garbage collection (GC) to delete those objects. Ever used a Flash profiler only to see a huge chunk of your CPU time going to [reap]
, [mark]
, or [sweep]
? Yeah, that’s the GC at work. Today’s article talks about some of the ways you end up allocating objects in AS3 without using the new
keyword. These subtle errors can end up costing you!
First of all, concatenating strings is an allocation. These innocent looking lines can be performance killers:
// 2 String allocations var fullName:String = person.first + " " + person.last; // N string allocations for (var i:int = 0; i < N; ++i) { trace("person #" + i); }
Next up are the Vector3D
and Matrix3D
classes. Sadly, such core built-in classes for 3D math are laden with allocations! (see Improving Vector3D for some ways around these)
// Allocates the result v = vec1.add(vec2); v = vec1.subtract(vec2); v = vec1.crossProduct(vec2); mat.transformVector(v); // Allocates a Vector.<Number> with length 16 mat.rawData;
And now the dreaded Array
and Vector
classes. These are chock-full of methods that allocate.
// Allocates a new Array/Vector with size a.length+b.length a.concat(b); // Allocates at least one String large enough to hold // the toString() representation of all the elements of // the Array/Vector plus the separator characters. If // the elements of the Array/Vector are not Strings, // allocates a String for every element a.join(); // Allocates the result Array/Vector with size a.length a.map(mapFunc); // Allocates a new Array/Vector with size N-M to hold the slice a.slice(M, N); // Usually allocates an array with size a.length a.sort(); // Allocates an Array/Vector with size N to hold the removed elements a.splice(index, N);
Some of the above can be distressing. For example, consider the totally standard “remove from a list” function:
function removePerson(name:String): void { var index:int = people.indexOf(name); if (index >= 0) { people.splice(index, 1); } }
Well, that does shrink the Array
/Vector
, but it also (counterintuitively) allocates an Array
/Vector
in the process! Consider this alternative:
function removePerson(name:String): void { var index:int = people.indexOf(name); if (index >= 0) { var len:int = people.length - 1; people[index] = people[len]; people.length = len; } }
This alternative moves the last person to the location of the person to remove and then cuts off the last element of the Array
/Vector
. This destroys the order of the list, but oftentimes order is not important. In those cases, you can save an allocation. If order does matter, you can instead use a placeholder element:
function removePerson(name:String): void { var index:int = people.indexOf(name); if (index >= 0) { people[index] = null; } }
You will, of course, need to adjust the rest of the code using the people
list to handle null
elements and probably keep your own count of the list’s size since its length
field will now be inaccurately high. All this trouble may be worth it though if you want to avoid allocation and preserve order.
Next, there are even some features of the language itself that result in allocation. Namely, anything that creates an activation object literally creates an activation object. You will notice in your profiler that an Object
has been created on your behalf when you use these features:
function foo(): void { // Allocates an "activation object" try { } catch (e:Error) { } } function goo(): void { // Allocates an "activation object" and a Function addEventListener(Event.ENTER_FRAME, function(ev:Event): void {} ); }
Lastly, for this list at least, is the seeming impossibility of creating a “ticker” to update a simulation periodically without generating tons of garbage objects. For example, consider the humble ENTER_FRAME
approach:
addEventListener(Event.ENTER_FRAME, onEnterFrame); function onEnterFrame(ev:Event): void { // The "ev" Event was allocated for us }
What about using a Timer
? It’s the same problem, except you get a TimerEvent
event every time your simulation ticks. How about an addFrameScript loop using tricky compiler settings and bizarre, undocumented MovieClip
functionality?
addFrameScript(0, onEnterFrame, 1, onEnterFrame); function onEnterFrame(): void { }
There’s no event, but you’ll see that every time the frame is entered an Array
(of what?) is allocated by the Flash Player. This bodes poorly for apps with a lot of code in FLA timelines…
In conclusion, beware of the many pitfalls of object allocation in Flash. The language is powerful and easy to use, but it’s also very easy to design code or use Adobe’s APIs in a way that allocates a ton of garbage. When you see 20%+ of your CPU time going to [reap]
, you’ll probably be spending some time chasing down problems like I’ve found above.
Know of more hidden object allocations? Share your experiences!
#1 by Henke37 on July 4th, 2011 ·
At least String.substr is nice enough to reuse the data of the original string. But you still end up with a new string and as such, a new object.
#2 by aro on July 4th, 2011 ·
In your second “removePerson” example, shouldn’t the code rather read:
instead of
?
#3 by Sebastien on July 4th, 2011 ·
Yeah, it’s definitely a mistake because the way it’s written in the article makes no sense.
#4 by jackson on July 10th, 2011 ·
Yep, definitely a mistake. I’ve updated the article to correct it and, as a bonus, optimized by removing an extra function call to the
length
getter. Here’s the new line that replaces both lines:#5 by skyboy on July 11th, 2011 ·
There is a problem with this though; I had the same thought, but later realized it sets the last index to undefined which is then inserted in the middle of the array, that’s many millions times worse and you lose an object entirely.
There is a way to reduce the cost ever so slightly (almost not worth it):
And a way to significantly reduce it for many (copy/pasted optimization around someone else’s code):
#6 by jackson on July 11th, 2011 ·
I should really just start testing these things before posting. ;-)
I’ve updated to your first version, without the bullets of course. :)
#7 by Daniel on July 4th, 2011 ·
That makes sense, I spent a bit of time trying to rationalize that.
#8 by Daniel on July 4th, 2011 ·
addFrameScript ? what the sh*t? I love undocumented functions. It’s like the as3 team decided that we cannot handle the truth.
Too bad that aside from saving some garbage collection there is no benefit, but I guess it adds up.
#9 by skyboy on July 5th, 2011 ·
It doesn’t save any garbage collection. At the minimum, an Array is allocated.
#10 by jackson on July 10th, 2011 ·
Exactly, it allocates an
Array
instead of anEvent
so you’re just trading one object type for another. :(#11 by Rackdoll on July 5th, 2011 ·
addFrameScript is actually prtty old alrdy.
Its ment for movieclip coding, on which you can add scripts on different frames.
#12 by skyboy on July 5th, 2011 ·
Luckily we can write our own sorting functions that are in-place, rather than use Array’s temporary allocation. Which has benefits on older machines, since they’re slower at allocating memory than newer machines are.
As for the mystery Array from addFrameScript, I imagine that either there’s a special private function for handling things passed to it (Getting an event, then checking frame/script mappings via an Array, probably) or an anon function is created that takes varargs and just calls the function object passed. luckily single argument varargs have never been terribly expensive, but either method should, logically, result in slower calls than just using an event listener.
Also, a planned feature is for AS3 code to be able to give hints to the GC about when to mark/sweep, so that should help ease the burden. Though there are other things Adobe can do, such as by not allocating a new event for everything that’s fired off in a specific thread: They can just hold a solid reference to a sealed Event that has its properties rewritten for each dispatch that’s applicable (enter/exit frame, activate/deactivate, etc.) and since only one function can execute at a time and threads (When they come) will only be able to share copies of data, rather than references (Oh my. The GC nightmares to come) no problems can arise from this, only benefits.
#13 by jackson on July 10th, 2011 ·
All good points. It pains me to see my game run with no object allocations except thousands and thousands of
ENTER_FRAME
Events
that I do absolutely nothing with and simply discard. A sealed version (is it already sealed?) or some other optimization for this extremely common usage would be very nice.#14 by skyboy on July 11th, 2011 ·
I believe Event is dynamic. Though due to how mmGC works, the overhead for the constant allocation and deallocation should be minimal, since that space is not released back to the OS and is of a fixed size, unless a property is added to the Event, then there’s an issue regarding where this new event gets allocated since it’s larger and probably doesn’t have a block of memory that becomes reserved for it through emergent behavior.
Just a theory though, since in the release player the memory may not be zero’d and is just simply over written instead, similar to how file systems work (memory fragmentation). A simple test would be to preallocate a Vector of 900 elements (sufficient for 1 event / frame at 30 FPS for 30 seconds) and queue the events until full, then flushing on frame exit with a listener that is not added until that point, and removes itself.
Following that up, would be a test with a vector of 9,000 and 10 listeners per frame.
There should be some change that takes minimal time in code and a lot of time on the player’s side. Such as a complex pattern with a full screen overlay that changes between alphas 0.25 and 0.75, alternating every frame to avoid any possible optimization by flash. Overlapped siblings behind it may result in the highest overhead, while not being remotely extreme, in terms of normal practices. With the alpha change on the single object in front of its siblings, movement should have zero effect on the player’s drawing code.
#15 by Rackdoll on January 6th, 2012 ·
Wow. Very nice article Jackson!
Have always thought native as3 functions were the fastest.
Never even thought that things like splice is heavy allocating etc.
Thanx for the enlightment.
#16 by Rackdoll on January 6th, 2012 ·
btw the onenterframe alternative is frecking genius!
very VERY nice solution
#17 by Cenfee on March 7th, 2012 ·
good!
#18 by Eduardo on July 5th, 2014 ·
Hi i know that this post is old but i run a small test on the array.splice vs the len method. And the splice was way faster.
Im doing something wrong or the compiler has improved since then?
#19 by jackson on July 6th, 2014 ·
Your code wasn’t in a <pre lang="actionscript3">CODE</pre> block, so it got somewhat mangled by the comments system. I think I see how the test was meant to run, though. Since the article is about the object allocation that
splice
triggers, you might want to test the other side of it to get a complete picture. I’m talking about the GC work that will need to be done to clean up the allocated objects. Unfortunately, this is hard to test since the GC runs on an unpredictable schedule.#20 by Eduardo on July 7th, 2014 ·
Hi for the quick reply on an old article.
Its true that i did not check GC on the test. But what about this method that i found online but cant remember the link.
I does not create a var and uses the vector pop() method to erase the last element of the array. **IT DISSORDER** the array but as you said in some cases it does not matter. And you did not listed “pop()” as an memory allocation method. So this would be ok to use?
#21 by jackson on July 7th, 2014 ·
Yes,
pop
can be used as a substitute forsplice
in one specific circumstance:Removing more than one element, inserting any elements, or actually needing the returned
Array
means you have to switch back to the more generalsplice
. However, if you happen to have that specific circumstance then you will probably get a nice speedup by usingpop
.#22 by Imran on March 9th, 2015 ·
Hi,
which type of initialization I should use.
Array or an Object.
Here is the code
var arr : Array = [int1, int2, boolean1, boolean2, boolean3];
or
var arr : myObject = new myObject(int1, int2, boolean1, boolean2, boolean3);
I know the first on is not readable though.
when I compared with getSize() API, Array initialization was taking 52 bytes compared to myObject which takes only 28 bytes.
My Question is which one to use?
Is there any hidden memory allocation for myObject which is not seen with getSize() API
#23 by jackson on March 9th, 2015 ·
I think it really depends on what
myObject
is. Is it a class you’ve made? If so, it very well could be a lot smaller than anArray
. That’s because theArray
has to handle a lot more than your class which could exist only to hold those variables. TheArray
may also have some extra capacity just in case you add more elements to it. In any case, a custom-made class is definitely the best way to go for minimal size.#24 by Imran on March 10th, 2015 ·
mYObject is a class. Thanks for your valuable input in such a short time.
#25 by Imran on March 9th, 2015 ·
Hi,
which type of initialization I should use.
Array or an Object.
Here is the code
1)
var arr : Array = [int1, int2, boolean1, boolean2, boolean3];
2)
var arr : myObject = new myObject(int1, int2, boolean1, boolean2, boolean3);
I know the first on is not readable though.
when I compared with getSize() API, Array initialization was taking 52 bytes compared to myObject which takes only 28 bytes.
Is there any hidden memory allocation for myObject which is not seen with getSize() API