Home >
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
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 ofHashMap:
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);
}
]]>
</mx:Script><mx:Label id="msg" />
</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
- Validation in Flex with Hamcrest-AS3
- Using UDP socket connections for low-latency and loss-tolerant scenarios in AIR 2 (Part 3)
- Using UDP socket connections for low-latency and loss-tolerant scenarios in AIR 2 (Part 2)
- Using UDP socket connections for low-latency and loss-tolerant scenarios in AIR 2 (Part 1)
- Advantages of Lazy Loading




Facebook Application Development
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.
Flash 10 introduces a new top level class Vector that is basically a typed array.
Benji,
You are correct. The benchmark could use improvement. It just takes time, which I have in very limited quantities.
Mike
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
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'...
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
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.
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.
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)
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.
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
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