Home  >  

ActionScript Data Structure Performance

Author photo
March 11, 2009 | | Comments (12)
AddThis Social Bookmark Button

As I have previously reported, lots of Java programmers are adopting Flex. One question that Java programmers always ask is "where are my beloved collections classes?"

First, the good news: this blog will point you to some excellent collection classes, and give you some information on what makes for a fast program. The bad news: ActionScript 3 does not support generics. Back to Java 1.4 land :(

I wrote a quick benchmark for a Flex version of HashMap, based on dynamic objects.  When I ran it in debug mode I found:
50,000 reads: 0.062 second
50,000 writes: 4.953 seconds

Obviously dynamic objects perform reasonably well for reads, but writes are horribly slow! Source code for this class and others, written for ActionScript using dynamic classes is available from http://code.google.com/p/j2as3. The benchmark programs mentioned in this blog are also available from the same Google Code project.

as3ds is an excellent data structures project for ActionScript, and it supports the following interfaces and classes:

  Interface Description
  Collection A 'java-style' collection interface.
  Iterator A 'java-style' iterator interface.
  LinkedList A marker interface for linked list classes.
  LinkedListNode A marker interface for the linked list nodes.
  Class Description
  AbstractLinkedList A marker interface for linked list classes.
  AbstractLinkedListNode  
  Array2 A two-dimensonal array.
  Array3 A three-dimensonal array.
  ArrayedQueue A queue based on an array (circular queue).
  ArrayedStack An arrayed stack.
  BinarySearchTree A Binary Search Tree (BST).
  BinaryTreeNode A binary tree node from which you can build a binary tree.
  BitVector A bit-vector.
  DLinkedList A doubly linked list.
  DListIterator A doubly linked list iterator.
  DListNode A doubly linked list node.
  Graph A linked uni-directional weighted graph structure.
  GraphArc A weighted arc pointing to a graph node.
  GraphNode A graph node.
  HashMap A hash table using direct lookup (perfect hashing).
  HashTable A hash table using linked overflow for collision resolving.
  Heap A heap is a special kind of binary tree in which every node is greater than all of its children.
  LinkedQueue A queue based on a linked list.
  LinkedStack A stack based on a linked list.
  NullIterator An do-nothing iterator for structures that don't support iterators.
  Prioritizable All objects stored in a PriorityQueue have to extend this class.
  PriorityQueue A priority queue to manage prioritized data.
  Set A set is a collection of values, without any particular order and no repeated values.
  SLinkedList A singly linked list.
  SListIterator A singly linked list iterator.
  SListNode A singly linked list node.
  TreeIterator A tree iterator.
  TreeNode A tree node for building a tree data structure.


Michael Baczynski, the author of as3ds, has a good writeup on the above data structures on his web site.

As3ds uses the Flash Dictionary class to implement HashMap, and the implementation is fairly faithful to the original Java class. Because get is a reserved word in ActionScript, the as3ds version of HashMap uses insert and find instead of put and get. I did not think that the names of the two methods were appropriate; to me, 'insert' connotes the unconditional creation of a new entry, and 'find' should return a reference, not a value.  I renamed the methods to getItem and put, and then created an svn patch so future versions could be similarly modified without fussing about:

Index: src/de/polygonal/ds/HashMap.as
===================================================================
--- src/de/polygonal/ds/HashMap.as	(revision 120)
+++ src/de/polygonal/ds/HashMap.as	(working copy)
@@ -74,7 +74,7 @@
 		 * @param key The key.
 		 * @param obj The data associated with the key.
 		 */
-		public function insert(key:*, obj:*):Boolean
+		public function put(key:*, obj:*):Boolean
 		{
 			if (key == null)  return false;
 			if (obj == null)  return false;
@@ -112,7 +112,7 @@
 		 * @return The data associated with the key or null if no matching
 		 *         entry was found.
 		 */
-		public function find(key:*):*
+		public function getItem(key:*):*
 		{
 			var pair:PairNode = _keyMap[key];
 			if (pair) return pair.obj;


I modified the benchmark to test the as3ds version of HashMap.  When I ran it in debug mode I found:
50,000 reads: 0.051 seconds
50,000 writes: 0.063 seconds

This shows that Dictionary is about the same speed as dynamic classes for reads, but about two orders of magnitude faster for writes.

After I disabled debug mode off by passing -debug=false to the compiler; performance increased about 300%, and was about 300x faster than the version using dynamic classes, running in debug mode:

50,000 reads: 0.016 seconds
50,000 writes: 0.015 seconds

I then tested Java 6's HashMap, using Java 1.4 source compatibility, and found:

50,000 reads: 0.016 seconds
50,000 writes: 0.015 seconds

Like as3ds, the Java implementation of HashMap does not show a significant performance difference between reads and writes, but it ran at the same speed as the ActionScript equivalent.

ActionScript Benchmark

Here is the benchmark program I wrote to test the ActionScript versions of
HashMap:

J2AS3CollectionsBenchmark.as
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
creationComplete="benchmark()"
xmlns:mx="http://www.adobe.com/2006/mxml">

<mx:Script>
<![CDATA[
import j2as3.util.StopWatch;
import de.polygonal.ds.HashMap;
import mx.formatters.NumberFormatter;
private var stopWatch:StopWatch = new StopWatch();
private var hashMap:HashMap = new HashMap();
private var numberFormatter:NumberFormatter = new NumberFormatter();

/** Might be a better test if it read six to ten values */
private function reads(readCount:int):void {
hashMap.insert("key", "value");
stopWatch.startTimer();
for (var i:int=0; i<readCount; i++)
hashMap.find("key");
stopWatch.reportElapsedTime(numberFormatter.format(readCount) + " reads");
}

/** Might be a better test if it wrote six to ten values */
private function writes(writeCount:int):void {
stopWatch.startTimer();
for (var i:int=0; i<writeCount; i++)
hashMap.insert("key", i);
stopWatch.reportElapsedTime(numberFormatter.format(writeCount) + " writes");
}

private function benchmark():void {
msg.text = reads(50000) + "; ";
msg.text += writes(50000);
}
]]>
&lt;/mx:Script>

&lt;mx:Label id="msg" />
&lt;/mx:Application>

StopWatch.as

package j2as3.util {
import flash.utils.getTimer;
import mx.formatters.NumberBaseRoundType;
import mx.formatters.NumberFormatter;

/** Measures and reports elapsed time. */
public class StopWatch {
public var millis:uint = 0;
private static var tenthsSecondFormatter:NumberFormatter = new NumberFormatter();
public function StopWatch() {
tenthsSecondFormatter.precision = 3;
tenthsSecondFormatter.rounding = NumberBaseRoundType.NEAREST;
}

public function reportElapsedTime(msg:String):Number {
var now:uint = getTimer();
var elapsedSeconds:Number = (now - millis) / 1000.0;
var reportMsg:String = msg + ": " + tenthsSecondFormatter.format(elapsedSeconds) + " seconds";
trace(reportMsg);
millis = now;
return reportMsg;
}

public function startTimer():void { millis = getTimer(); }
}
}


Java Benchmark

Here is the program I wrote to test the Java HashMap:

HashMapBenchmark.java


import j2as3.utility.*;
import java.text.NumberFormat;
import java.util.HashMap;

/** Test of Java 1.4 style collection performance.
* Meant to mirror the ActionScript benchmark, not to be terribly useful. */
public class HashMapBenchmark {
private StopWatch stopWatch = new StopWatch();
private HashMap hashMap = new HashMap();
private NumberFormat numberFormat = NumberFormat.getNumberInstance();

/** Might be a better test if it read six to ten values */
public void reads(int readCount) {
hashMap.put("key", "value");
stopWatch.startTimer();
for (int i=0; i<readCount; i++)
hashMap.get("key");
stopWatch.reportElapsedTime(numberFormat.format(readCount) + " reads");
}

/** Might be a better test if it wrote six to ten values */
public void writes(int writeCount) {
stopWatch.startTimer();
for (int i=0; i<writeCount; i++)
hashMap.put("key", new Integer(i).toString()); stopWatch.reportElapsedTime(numberFormat.format(writeCount) + " writes");
}

public static void main(String[] args) {
HashMapBenchmark benchmark = new HashMapBenchmark();
benchmark.reads(50000);
benchmark.writes(50000);
}
}


StopWatch.java

package j2as3.utility;

import java.text.DecimalFormat;
public class StopWatch {

public long millis = 0L;
private static DecimalFormat tenthsSecondFormatter = new DecimalFormat();

public void startTimer() { millis = System.currentTimeMillis(); }

public double reportElapsedTime(String msg) {
long now = System.currentTimeMillis();
double elapsedSeconds = (now - millis) / 1000.0;
System.out.println(msg + ": " + tenthsSecondFormatter.format(elapsedSeconds) + " seconds");
millis = now;
return elapsedSeconds;
}
}

_______________________________

Mike Slinn
Independent full-service software contractor and author
http://slinnbooks.com
http://mslinn.com

Read more from Mike Slinn. Mike Slinn's Atom feed mslinn on Twitter

Comments

12 Comments

Benji Smith said:

Your benchmarks are a little dubious.

Since you always insert the same key ("key"), your data structures never contain more than one entry!

I'd recommend something more like this:

   for (int i = 0; i < n; i++) {
      map.put(i, i);
   }

One of the important characteristics of a hashtable is how it deals with bucket allocation and duplicate-hash mitigation. Your benchmark doesn't allow those considerations to be taken into account.

Nathan Levesque said:

Flash 10 introduces a new top level class Vector that is basically a typed array.

Mike Slinn said:

Benji,

You are correct. The benchmark could use improvement. It just takes time, which I have in very limited quantities.

Mike

Mike Slinn said:

Nathan,

Vector would make a good enhancement to the J2AS3 syntax converter (http://code.google.com/p/j2as3). Thank you for pointing that out.

Mike

aladdin said:

the main reason of slow write is that the collection class is badly implemented, for instance, from j2as3.collection.HashMap

public function getIndex(index:int):* {
var i:int = 0;
for (var key:String in this) {
if (i++==index)
return this[key];
}
return null;
}

how could this program run fast? if java's HashMap is implemented the same way, java WILL RUN VERY SLOW. Linear search, no hashing key, no sorting.... no, no, no, please take a book about data structure and algorithms, and re-read about sorting and searching.

After you have written some 'qualified' code, then you can say something about 'BENCHMARK'...

Mike Slinn said:

Aladdin,

Please remember that the test data only used one key, therefore none of the issues you raise have any effect on the results. If the test data was more demanding, and the implementation was more robust, the end results would have been virtually identical.

My point was that dynamic classes are *very* slow. That point stands. For a well-designed data structure library, look to as3ds. I did not recommend any other library.

Mike
... who appreciates courteous discourse

Anonymous said:

I just started to use the library and immediately i noticed some unwanted behavior. The library itself might be good but i think the documentation is a bit sub-standard which will sooner or later cause bugs in the program using it. You can use the lib, but you need to check the source code to see what is exactly happening, because the documentation itself is not enough. Don't assume the library does what is commonly regarded as a "sane approach", because it doesn't.

jdv145 said:

Looking into the HashMap.as of the library, i noticed it uses a Dictionary with weakKeys for the stuff which is being stored.

I have never used a dictionary and i just glanced over the code, but doesn't the usage of a weakly reffed dictionary mean that the objects will be gc-ed if the hashmap has the last reference to it? So what would happen if a naive programmer would use this hashmap for data storage ..?

Like i said in the post above, the documentation is a bit shaky and the code doesn't really give me any confidence either. I'm looking for an alternative now.

Anonymous said:

Just wanted to tell i found a lib which gives me more confidence. Although im sure as3ds works fine most of the time, the libraries on http://www.ericfeminella.com/blog/actionscript-3-apis/ look more compelling to me. It has clear examples and the documentation describes much better what the functions do.

(sry for the 3 posts in a row, ill refrain myself from further commenting)

Jens said:

First of all, providing a library for free is always to appreciate. If you experience inconveniences or troubles, this shouldn't go to the authors balance. You are always free to improve or modify the sources to your taste. As well as implementing and releasing your own approach.

... so I did with the Lite Collections for ActionScript, which I would like to mention here. Without going too much detailed, the main distinctions to the polygons project are:

- standard conform object oriented approach
- more a general solution than providing context specific optimisations
- especially focused on maps and sets as well as on sorted structures (sorted map, set, list)
- beats the as3ds performance in the most of the cases ;D

The Lite Collections project probably will become part of the ActionScript standard library as3commons.org, which is a pretty new initiative to get rid of the unnerving reel invention.

http://sibirjak.com/blog/index.php/collections/lite-collections-for-actionscript/

Thanks for attention.

Kirk said:

Actually insert() is aptly named because it doesn't work the same way as a put(# would. insert## doesn't do anything and returns false if you try to assign a different value to an earlier inserted key. a put#) would normally replace the value.

The only way to reassign a value to a key that I have found is to remove the old value first.

When I googled to see if there was any information about it, it came up with this post, hence my very late comment :^P

Nash said:

Great comparision and advance tips on flex

Basic link for newbie to learn how to read java hashmap in flex
http://yasob.blogspot.com/2009/09/retriveing-values-from-java-hashmap-in.html

Leave a comment


Tag Cloud

Question of the Week: New Year

What are you most excited about for 2010?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.