ActionScript Read/Write Performance

| 15 Comments

Reading, Writing and Arithmetic

ActionScript 3 (AS3) on FlashPlayer 9 has delivered on its promise of much improved performance. Sure, we'll probably always wish it could be faster, but it is much better than ActionScript 2 (AS2). In AS2, everything was essentially dynamic. You could add properties to classes at runtime, change their types, modify methods, and all kinds of evil tricks. This required a certain amount of overhead at runtime to allow for this because you couldn't know up front what a property was and how to access it.

AS3 introduced 'sealed' classes (classes that are not dynamic). Sealed classes cannot have properties added to them or those other nasty AS2 tricks. As such, the runtime can make assumptions about how to access that property. AS3 also implemented Ecmascript For XML (E4X) which made it possible to access the pieces of an XML hierarchy as if they were all objects, using the '.' syntax. This is a vast improvement over the old way of working with XML in AS2.

One of the prices paid for this faster access is the notion of change detection. If objects do not have change detection by default, you can write to them faster as well. However, if you want to detect changes you have to add code to do so. AS3 provides a Proxy class that builds in change detection, and Flex provides [Bindable] metadata that the compiler detects and generates different code to handle change detection.

This means that there are a variety of objects (sealed, dynamic, proxy, bindable, XML, etc) that you can read from and write to in your application. These different objects have different read and write times. The basic runtime of AS3 is very fast so most if the time you won't care about this information and will use the type of object most convenient to you, but in case you're wondering, I put together a little test to see how fast things really are.

The test methodology is pretty rudimentary. Call getTimer(), save away the value, run a sequence of code, call getTimer() again and see what the difference is. Various things can cause errors in such testing, but a couple of runs showed reasonable consistency since I was most interested in relative time instead of absolute time. As such, your mileage will vary based on your environment, but here's what I learned:

Reading

As promised, reading a variable from a sealed class is very fast. My tests accessed variables 200,000 times in just 2ms or less! Everything else was notably less fast, but remember, most of the time, this won't matter to you, or the cost of optimization may be prohibitive, but here are the numbers anyway. The results will be reported in the following format: a title, a number, and my thoughts on the results. For this first batch of tests, the basic loop looks lke:

for (i = 0; i < counter; i++) {
s = obj.label;
s = obj2.label;
}

where obj and obj2 are local variables typed as sealed classes, Objects, XML or a Proxy subclass, so the compiler knows exactly how to access the data

Reading From Dynamic Object: 40

This means that reading from a dynamic object (created using { label: "foo" }) took roughly 40ms compared to the sealed class variable's time of around 2ms. This implies that reading from a dynamic object is 20 times slower than reading from a sealed class. So, if you know the set of properties and access them often, define a class instead of using Object. You'll also get compilation errors if you mis-type a property name.

The reason I'm just using numbers and not times in this report is because the actual amount of time will vary based on your computer's speed, so numbers give you a relative feel. You'll see that nothing else runs its test in 2ms, and that there are numbers in the 40-100 range which means that the difference is probably ignorable since it would take 2,000,000 reads to cost your application 400ms such that someone might notice a half-second. Scenarios with numbers in the 300-800 range might we worth your consideration for optimization, but remember, it still took 200,000 reads or writes to add up to less than a second of time.

Anyway, here's the rest of the results.

Reading From Proxy: 350

That's right, reading from a Proxy object is significantly slower!. But if you need change detection, you have to pay a price. While that number seems scary, note that it took 200,000 accesses to cost and additional 350ms, so like I said, many times you won't notice.

Reading From Sealed Classes via Getters: 38

Many times, you'll actually implement properties as getter/setter pairs instead of plain 'vars' because they can be overridden, more logic can be added to the getters or setters (especially to do change detection), or because the properties are defined in an interface. In fact, the vast majority of properties you access in a Flex app are getter/setters so you're almost always paying this overhead. It is essentially just as fast as dynamic objects, but is much faster than Proxy if you also need change detection. Note also that these numbers are for the simplest getter that just returns a backing variable.

Reading XML Attributes: 550

Yes, that's right, E4x is slow. But it sure is convenient, and if you had to use other functions to get at the attribute, it would be even worse. However, if you access the same attributes many times, it might pay to convert them to sealed classes or dynamic objects first. And this is simple obj.@label access, not some deeper fetch.

Reading XML Sub-Node: 750

Reading sub-nodes is even slower than reading attributes, so if you have a choice, attributes will be faster, but still not like converting to sealed classes or objects.

More Results

The first set of tests assumed you knew the type of the object you are reading. Often, we don't know, or don't know right away. For example, in an event handler, the event.target is typed as Object but you often know it is some other type, and arrays are not typed so when you are indexing into an array to get an object, you have to think about whether to coerce to a known type or not. So, the next set of tests I ran examined reading and writing when pulling objects from an array. The basic loop looked like:

for (i = 0; i < counter; i++) {
obj = arrObject[0];
s = obj.label;
obj = arrObject[1];
s = obj.label;
}

where the arrObject array might contain sealed classes, XML, etc, and obj is or isn't strongly typed.

Reading From Sealed Class Variables (Coercion): 24

The benchmark for these test is reading when we have a local variable of the correct type and coerce the array entry via:

var lv:SomeClass = SomeClass(arrClass[1]);

That more or less says the price of coercing 200,000 times was some 24ms on my computer, so it is very inexpensive and will give you type-checking at compile time.

Reading From Sealed Class Variables (as): 30

This implies that coercion using "as" is slighly slower than coercion via a function as in the previous test.

Reading From Sealed Class Variables as Objects: 90

This says that it actually pays to coerce objects to known types before accessing variables, otherwise your reads can be 3 times slower.

Reading From Sealed Class Getters: 60

This says that coercion overhead starts to drown out the difference between access via vars vs getters, but there is still a cost. However, most of the time you don't really have a choice whether to use vars vs getters.

Reading From Bindable Class: 60

This says that if you make a class [Bindable] you are converting the vars to getters and paying the required price for doing so.

Reading Objects: 45

This says that it is worth the cost of defining sealed classes and doing the coercion in the loop, unless the sealed class is using getters in which case it is slightly faster. I'd still go with sealed classes for the type-checking benefits.

Reading Proxys: 370

Proxys are slow. You should use [Bindable] on sealed classes instead. Note also that ArrayCollection is a Proxy so [] access is way slower than [] access of the internal array. It's probably even faster to use getItemAt() than [] access.

Reading XML Attributes: 575
Reading XML Sub-Nodes: 770

XML is even slower than Proxys. As mentioned earlier, you'd have to access the same attributes quite often in order for it to be worth the cost of converting the XML to objects, but there'll be occasions where it is worth it.

Writing

A similar loop was used to test writing to properties. We take objects from an array, coerce to a local variables if necessary, then write to a property. The results are:

Writing To Sealed Class Variables: 450

Reading may be fast, but writing is still slow, almost 20x slower than reading (450 / 24).

Writing To Sealed Class Setters: 930

Writing to the most simple setter that dispatches a change event is twice as slow as writing to variables.

Writing to Bindable Sealed Class: 1030

Bindable is slightly slower because it takes the time to do a value changed check before dispatching an event.

Writing to Dynamic Object: 500

Sealed classes are slightly faster unless they use setters, but that's the price you pay for change detection and compile-time checking

Writing to Proxy: 830

Proxy is only better in this case because it didn't send a change event. If it did it would probably be slower than sealed classes with setters.

Writing to XML Attributes: 580
Writing to XML Sub-Nodes: 950

No surprise here. XML isn't very fast, but is very convenient.

Writing to XML Attributes That Have Binding: 7700
Writing to XML Sub-Nodes That Have Binding: 8900

This is the big surprise. XML data binding is very expensive in terms of the cost of modifying the XML data.

Arithmetic

The main takeaway is that XML is slower than objects, but its convenience might outwiegh the performance considerations. Note, though, that anytime you shove XML into an XMLListCollection all of the nodes get setup for binding so the cost of modifying those nodes might be measurable if you do a lot of modifications, and conversion to object may reap benefits.

There's no easy way to test the speed of converting an XML tree to objects. Our WebService code has functionality that does the conversion, but the speed will depend a lot on the kinds of XML data you are getting. However, it has occurred to me that the conversion code converts the entire tree, and maybe a custom collection could convert on the fly and save the big hit of converting lots of nodes if the query returns a large chunk of XML when only a few nodes might be seen in a list. Hopefully I'll get to that someday soon and blog about it.

Other than that, unless you really care about saving a tenth of a second here or there, you probably don't have to think about which data types you are using, but if you want to impress your friends at holiday parties, you can memorize these numbers and recite them. To decide whether to optimize is pure arithmetic. Look at what it would take to convert to a faster access method vs how many accesses you are doing and what the expected payoff might be and decide from there.

Credits

Thanks to Scott from Fast Lane and Tracy Spratt for getting me started on this topic

Additional Information about XMLListCollection

In Flex 2.x, XMLListCollections eat lots of memory. That's how I originally got started on this topic as Tracy pointed me to Scott's blog. I'm unconvinced that it is really a leak, but the memory utilization is such that it doesn't matter. This kind of memory utilization can cause paging in the operating system and exacerbate the performance cost of using XML. Changes were made in Flex 3 Beta 2 to address this problem and memory utilization is better and bounded, but still not as good as objects, so some of you may also want to convert from XML to objects for this reason as well.

15 Comments

Very interesting article Alex. I was surprised by the E4X results -- I didn't think it was that slow. But I was more surprised by the performance loss from using getters/setters. I was under the impression that the runtime engine was actually more optimized for those. Is this a case of "no good deed goes unpunished?"

-----------------

Everything has a cost, but realize that getter overhead is largely ingorable as it would take 2 million gets to waste half a second on a fast computer. However, I did point this out to the player team in case they can think of an optimization for simple getters and setters.

Could you post your source for this (particularly writing)? I've been running an extended series of tests: 200,000 repetitions, setting a property on a fixed class and it takes only 34ms.

for( i=0;i obj.label="foo";
obj.label="bar";
}

I was trying to benchmark my system against yours as I was testing things like Singleton access and more complex Binding. Your article seems to say that the above write test took 240ms. That kind of difference made me question my methods.

-------------------------

The writing code looks like:

for (i = 0; i {
obj = arrObject[0];
obj.label = "foobar" + i.toString();
obj = arrObject[1];
obj.label = "foobar" + i.toString();
}

It probably takes longer than yours because it is generating new strings before every assigment, which is required to get past the (oldValue != newValue) test in [Bindable] properties.

So, thanks for pointing out pure writes is very fast, but you will need to make new values before writing in order too make a fair test for all scenarios and all we're really concerned about anyway is relative time.

You might want to change your write benchmarks to look more like

var val1:String = "val1";
var val2:String = "val2";
for (i = 0; i < count/2; count++) {
obj = arrObject[0];
obj.label = val1;
obj = arrObject[1];
obj.label = val1;
obj = arrObject[0];
obj.label = val2;
obj = arrObject[1];
obj.label = val2;
}

Then your write test wouldn't be dominated by the string allocations, but would still trigger changes.

------------------

Good suggestion. I'll try it someday. Did it make a difference in the relative write times?

Hi Alex,

Regarding memory lost and system performance, I want to focus on some problems for not so big RIAs:

a) Flex Builder 3 profiler problems seem not to be fixed (in both, beta1 and beta2)

b) When adding a child to a component using addchild inside a "state" they delay their creation until their first usage (great!!), but when they are not used any more they continue using system memory (and it seems there is not solution for that!!).

c) when creating dynamically from a XML objects (even when specifying recycleChildren attribute "true") the memory assigned will not become free when the children are destroyed/eliminated.
For futher information:
http://www.mail-archive.com/flexcoders@yahoogroups.com/msg39086.html

d) Several bugs about memory reservation and recovery continue unsolved and are posted in:

http://bugs.adobe.com/flex

I am a big Macromedia/Adobe RIA enthusiast for long time ago, but many of the problems commented above are making to migrate our applications to other RIA platforms. This is due to the memory wasting that makes our users to restart the Web application continuously to perform their tasks.

Thanks a lot.

---------------

Please make sure bugs are filed for the issues you care about and vote on them and get others to vote on them. If you want to send me a prioritized list of bug numbers I'll try to take a look at them. Unfortunately, timing is running out for this release so please hurry.

Great post! I have been very interested in AS3 benchmarks, while trying to come up with some basic guidelines for AS3 optimization and this is very helpful.

I was hoping you could post the source code?

I've posted some benchmarks at http://jlgauthier.com/blog/?p=3 and I'm especially interested on how your benchmarks hold up across pc/mac/linux and public/debug players?

------------

I'll try to publish that someday. It is a pain to format code in this blog. Excerpts are in the article. You may be able to derive the test from there. I would expect other platforms to show the same relative time, but you never know...

Thanks a lot - great article!

Very nice - thank you!

One other useful little thing. If you have a fairly large XMLList that you are accessing by index (e.g. myXMLList[number]), you might want to consider creating an array of the XML objects in the list. Accessing Arrays by index seems to be MUCH faster than accessing XMLLists by index. This is in Flex 2, so they may have fixed this in Flex 3.

---------------------------
Alex responds:

I'd bet it is the same in Flex 3. The lesson here is that taking the time to convert to objects might be useful if you access XML frequently.

The subject of a very wonderful and distinct
I thank you for continuing excellence
Thank you

very nice working.
thanks

This theme is not trivial, i will see - but great work - thank you!

good article indeed

great work in this article
good luck in your future
reports!

Many Thanks and Regards from Germany, really an excellent and usefully Article!

Leave a comment


Type the characters you see in the picture above.

About this Entry

This page contains a single entry by Alex Harui published on October 3, 2007 9:22 PM.

Popup Dialogs as Modules was the previous entry in this blog.

Debugging Tricks is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.