Why Bubblemark is a poor ui benchmark

A few months ago someone on the Adobe boards asked why the Flex testcase in Bubblemark seemed to act so different in AIR versus in the browser. Yesterday, I saw the same question come up again and I figured I’d finally weigh in on the topic. The simple answer is that the test was created improperly, the complex answer has to do with the inherent limitations of the test itself.

First off, for those who don’t know what the Bubblemark test is, its a simple animation test case implemented in different GUI frameworks, its kinda like an Acid2 test for rendering speed. The charts should ideally give you a base number to understand how well one technology compares against another for rendering. As a GUI developer I’ve been a bit underwhelmed with the whole thing and heres why:

  1. The author doesn’t understand Flash’s rendering engine. The easiest way to illustrate how incorrectly the Flash test was designed, is to download the source and change the compiled framerate to 1 fps. Re-compile and run the test and you’ll notice the benchmark framerate running at ~50 fps. You can clearly see the balls only moving once per second, yet the test thinks its flying along. This is because the testcase makes the incorrect assumptions that changing the properties of a DisplayObject causes it to render right away. The reality is, Flash holds on to all display updates till the next render pass and applies all the latest changes at once. Changing the position of an object every 5 milliseconds is meaningless when Flash is bound by a 33 millisecond render pass (or whatever you’re framerate divided by 1000 happens to be). A correct test case would rely on an ENTER_FRAME handler to change x and y values and get rid of any Timer calls.
  2. Framerate tests above 60 fps are meaningless. Seriously, any GUI benchmark designed to test above 60 fps is bogus. In fact, a pretty simple optimization technique for Adobe or Sun would be to cap the paint requests that get forwarded to OS X or Windows, simply because the majority of computer users these days are on LCD panels which natively run at 60 fps. Some operating systems even go a step further and limit the effective framerate of paint requests it sends to the videocard (see Beam Sync on Mac). So when you see the Java test case fly up to 120 fps on Bubblemark, you can realistically only see 60 of those frames, and there might be a chance the other 60 are never even calculated by Javas layout engine.
  3. The test just moves balls around! This is my biggest beef with the benchmark because it only tests one simple aspect of the rendering engine in these technologies, which is bitmap translation. How do bitmaps moving around the screen tell you anything about the capabilities of the respective technologies? Do the JavaFX guys really think optimizing this usecase will make their technology relevant? The only thing Bubblemark will tell you is which runtimes might best handle bitmap particle emitters….thats about it. Theres a lot more that goes into both the layout engine and the rendering pipeline of these different technologies and its a shame that only the most basic aspect is being tested. The funny thing is, if you open up your task manager while running the tests, you’ll notice that several of them don’t even try to run at full speed, my CPU is sitting as low as 20% in some cases. This means the runtimes don’t even consider the test difficult enough to give it full attention and have opted for using less power over faster motion.

I don’t mean to cut down the developers responsible for Bubblemark because at least they came up with a simple way to help us all compare these different technologies, I just think its a bit misguided to put any meaning behind these numbers. When evaluating your options for a GUI framework in our flashy web 2.0 world, you need to consider how well a technology can handle object scaling, alpha transparencies, rotations, text reflow, along with basic x and y translation and dynamic redraws. Even more realistically, developers need to be aware of the limits in the 25-45 framerate region since this is where you can efficiently balance render complexity with smooth animation. I’ve uploaded a couple quick test cases in Flash, HTML, and Silverlight that I think provide a good foundation for stressing a rendering engine and hopefully I’ll get a chance to expand them more into a full test suite.

Kick starting the garbage collector in Actionscript 3 with AIR

During the final months of my work with eBay Desktop, my sights were set squarely on optimization, both memory and cpu. When it came time to start messing with the garbage collector, my sanity went from bad to worse. What I originally thought was going to be a straightforward way of releasing memory in AIR turned into a 2 month long testcase with some discouraging outcomes.

Memory usage in eBay Desktop was something that always lingered in the back of my mind during the entire development cycle. Because of the shifting nature of our requirements, the issue was only explored near the end, even though we knew in the beginning that memory usage had to go through valleys and peaks in a managed way while users spent time in it throughout the day. We had alerts that would open and close, we had an app that would run in the system tray, and we considered making a ‘lite’ mode that would sit on top of your desktop all day, each of which had different memory requirements. In the beginning we used a well known hack to test garbage collection until part way through the development of AIR and Flex 3 Adobe finally added support for calling the garbage collector directly. We knew at this point we had a viable route for managing memory since Adobe was now making the effort to acknowledge the need. So now all that was required was a simple call to

flash.system.System.gc()

and we were set to go….right?

Well, not exactly. First off we learned that a call to System.gc() only does a mark OR a sweep on any given object, but not both in the same call. So in order to have the effect of releasing memory back to the OS, we needed to call it twice in a row. One call to mark any dereferenced objects and sweep away old marks, and the second to now sweep away marks from the first call.

flash.system.System.gc();
flash.system.System.gc();

Now this seemed to be releasing memory back with pretty basic test cases, but it wasn’t working under production scenarios and we had to turn to Adobe engineers to help with the problem. What we learned was that you have to contend with 2 different kinds of pointers when working in AS3; pointers that exist in bytecode, and pointers that *may* exist in the Flash player itself that you’d never know about. What I started to realize was that the Flash player was never really engineered to be aggressive about memory usage. It was designed to plug memory leaks and manage memory plateaus, but not designed with an assumption that users would be interested in lowering those plateaus. It makes sense because most Flash content is viewed in the browser for a short amount of time before the plugin is destroyed and all memory is released when a user navigates away. With AIR, the rules changed since users are more conscience of discreet application memory usage and applications might not always need the same memory when launched vs after 2 hours of usage.

First up we found that the Flash player was always maintaining a reference to the last Sprite clicked, so if you destroyed an AIR window that the users had interacted with, you couldn’t get garbage collection to work until interacting with another window, which can become a big problem if you’re running in system tray mode and there are no windows to click in. Secondly we learned that you have to push any existing enterframe handler off the call stack by creating a new one. Adobe took care of the first problem, but to handle the second one we had to change our GC call a bit.

private var gcCount:int;
private function startGCCycle():void{
	gcCount = 0;
	addEventListener(Event.ENTER_FRAME, doGC);
}
private function doGC(evt:Event):void{
	flash.system.System.gc();
	if(++gcCount > 1){
		removeEventListener(Event.ENTER_FRAME, doGC);
	}
}

Another facet we hadn’t considered was the affects of the Flex framework on garbage collection. Flex kept some of the same design philosophy as the player itself, mainly that end users were loading applications in the browser and then navigating away when done. Garbage collection was therefore considered on a micro level involving user components, but not at the framework level which could be guaranteed to exist throughout the life of the app. Adobe made strides on patching the framework to work better in discreet Windows, but ultimately some things couldn’t be changed. What we found was that CSS could not be defined in any <mx:Window> component. It had to be defined in the root <mx:WindowedApplication> which would take care of declaring CSS globally for all windows. Also we were forced to clear some global variables ourselves, which caused our code to now look like this.

private var gcCount:int;
private function startGCCycle():void{
	ContainerGlobals.focusedContainer = this;
	gcCount = 0;
	addEventListener(Event.ENTER_FRAME, doGC);
}
private function doGC(evt:Event):void{
	flash.system.System.gc();
	if(++gcCount > 1){
		removeEventListener(Event.ENTER_FRAME, doGC);
	}
}

Lastly, not all features in AIR could be unhooked with our enterFrame trick, after another couple days of testing we found components that needed to be unhooked with Timers like the HTML component. One last tweak to our garbage collection cycle and we were home free.

private var gcCount:int;
private function startGCCycle():void{
	gcCount = 0;
	addEventListener(Event.ENTER_FRAME, doGC);
}
private function doGC(evt:Event):void{
	flash.system.System.gc();
	if(++gcCount > 1){
		removeEventListener(Event.ENTER_FRAME, doGC);
		setTimeout(lastGC, 40);
	}
}
private function lastGC():void{
	flash.system.System.gc();
}

We were now able to successfully garbage collect any objects that have been dereferenced in Flash. We had three things we had to look out for in the app now.

  1. All display objects that added listeners on to model data had to be weakly referenced or they wouldn’t be automatically dereferenced. This is because our architecture kept model data alive while individual window stages were being destroyed. I feel like I should point out that contrary to some beliefs, it is not a good idea to apply weak references by default throughout your entire app. Trust me when I say that its alot easier to debug an application with memory leaks due to strong listeners, then it is to debug an app in which users report random failures because underneath the hood weakly referenced objects are getting accidentally destroyed when the GC kicks in. You can never avoid bugs, so you should program in a way that makes them consistent to find.
  2. All asynchronous events needed to be explicitly shut down. This included Timers, Loaders, File and DB transactions. Setting these to be weakly referenced is not enough as all asyncronous objects in AS3 register themselves to the Flash player while they are running. It is impossible to access objects that have been dereferenced in code but continue to be referenced by the player like a running timer.
  3. No anonymous closures allowed.

After all this was taken care of we began to learn that garbage collecting objects in Flash didn’t translate so easily to releasing memory back to the OS. If you ever look at the memory graph in the Flex debugger and then open up the Task Manger or Activity Monitor to compare memory usage, you’ll notice a huge disparity between the two.

Flex Builder memory profiler
FlexBuilder reports only 15mb of AS3 object data

AIR system memory profile
AIR private memory really takes up 65mb on the system

The difference you’re seeing is Object Memory vs. Rendering Memory and the bulk of all memory used by the Flash player goes toward rendering. Displaying an empty stage in Flash can take up anywhere between 10mb and 20mb depending on the width and height and then it climbs by roughly 4k for each display object attached to the stage. This can add up quickly when using the Flex framework where even a simple button uses several display objects.

What we ultimately found was that even though we could successfully release AS3 objects, we couldn’t reliably get the Flash player to release render data. So an app that started at 20mb would climb to 100mb, and when the entire stage was destroyed, we’d go back down to only 80mb. You can actually test this for yourself by downloading a sample Flex 3 project and observing the effects. What I’ve learned from Adobe is that the player ends up fragmenting memory quite a bit. It probably goes back to the heart of the initial design of the Flash player I described above. All the effort was put into making a killer GUI environment that deferred memory management to whether the browser window was open or not, and as a result the memory system was not optimized for more aggressive use cases. I can only guess the problem comes from the fact that AS3 code is attached to the timeline of the player itself, and managed by the elastic racetrack. As a result both AS3 objects and render data get mixed in to the same memory page, and releasing just the render data doesn’t matter when model data is placed on the same page.

Adobe has assured me they are working on the problem but it realistically won’t make it in until after AIR 2.0, and its unclear whether those fixes will be merged into the the plugin player given that the need isn’t very high for web pages. Until then, you can not count on creating an application in AIR that releases memory back to the OS. The best approach is to reuse the smallest amount of displayobjects possible to achieve the desired workflow. Get used to adding and removing children without destroying them but instead sliding them off into a pool for later reuse, as this helps keep the memory plateau low during use.

Saving class data to disk in AIR

For eBay Desktop we wrote a simple utility for reading and writing classes directly to disk. Using this method we’re able to load the users existing data from disk, or if its unavailable, create a new class with all the defaults.

First up is the utility class AIRUtils

package com.effectiveui.util
{
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;

	public class AIRUtils
	{
		public static function readFileObject(fil:File):Object{
			var amf:Object;
			if(fil.exists){
				var stream:FileStream = new FileStream();
				stream.open(fil, FileMode.READ);
				amf = stream.readObject();
				stream.close();
			}
			return amf;
		}
		public static function writeFileObject(fil:File, obj:Object):Object{
			var stream:FileStream = new FileStream();
			stream.open(fil, FileMode.WRITE);
			stream.writeObject(obj);
			stream.close();
			return obj;
		}
	}
}

These 2 static methods act as simple helper wrappers to read and write classes out to disk.

The example class we want to save below looks very similar to the type of class we use inside eBay Desktop.

package com.ebay.model
{
	import com.ebay.model.SearchPreferences;

	[Bindable]
	[RemoteClass(alias="com.ebay.model.UserPreferencesModel")]
	public class UserPreferencesModel
	{
		public static const STANDARD:String = "standard";
		public static const SKINNY:String = "skinny";

		public var logoutOnClose:Boolean = false;
		public var viewState = STANDARD;
		public var globalSearchFilters:SearchPreferences = new SearchPreferences();

		private var _maxHistoryItems:uint = 1000;
		public function set maxHistoryItems(max:uint):void{
		   _maxHistoryItems = max;
		}
		public function get maxHistoryItems():uint{
		   return _maxHistoryItems;
		}
	}
}

In order to write a class out to disk and read it back in, you have to add RemoteClass metadata to it. It doesn’t matter what the value is it just needs to be unique to the application. This provides Flash with an identifier for linking amf data to class definitions when its loaded back in. It doesn’t matter what value you put in the RemoteClass tag, but the best practice is to use the class name of the model you’re saving. Additionally, in this example we’d need to make sure both UserPreferencesModel and SearchPreferences model have RemoteClass metadata, since caching the UserPrefencesModel will automatically attempt to cache the SearchPreferences as a child model.
Only public variables will be cached to disk, so both the static values and the private var will be thrown out, but the public getter/setters will be cached.

Interacting with the cached data using AIRUtils looks like this.

private var fileRef:File = File.applicationStorageDirectory.resolvePath("UserPreferences.dat");
private var userPrefs:UserPreferencesModel;

private function loadPrefs():void{
	userPrefs = AIRUtils.readFileObject(fileRef) as UserPreferencesModel || new UserPreferencesModel();
}
private function savePrefs():void{
	AIRUtils.writeFileObject(fileRef, userPrefs);
}

When loadPrefs() is called, the cached class is read in and cast as a UserPreferencesModel. If no file exists in the users cache, we end up creating a new class and the defaults defined in the class will be used. This ensures we have something to work with, all within 1 tidy line of code. To save out the file we just pass the file reference and the class instance to AIRUtils.writeFileObject() and we’re done.

eBay Desktop released!

After more then a year and a half of development, we’ve finally been able to release eBay Desktop 1.0. After starting as a simple prototype and slowly growing into a viable product, its been a tremendous effort to create. Having lead the development of the app, I’m relieved to see this beast finally released and I’m pretty excited to see how useful it becomes to the eBay user community. I’m hoping to spend the next months worth of posts discussing some of the Flex and AIR techniques used in the app.

RegExp pattern for creating file safe names

Different operating systems restrict specific characters from being used in file and folder names. The following code snippet will allow you to trim those special characters when creating File references in AIR.

var fileSafePattern:RegExp = new RegExp('["\\\\ *?<>|:]', 'gi');
var fileSafeName:String = myString.replace(fileSafePattern, "_");
var myFile:File = File.applicationStorageDirectory.resolvePath(fileSafeName);

eBay Desktop demo

I’ve been doing a few presentations on Apollo the past few months centered around accessing eBays web services. While the app shown has remained just a “prototype”, I’m finally able to talk openly about the product and the fact that we’re moving into an official eBay application with a beta program coming soon.

The beta signup is public now so if your interested in getting involved early on and getting your feedback heard, you can sign up over at http://www.sandimasproject.com . I’m planning on watching the results of the beta very closely and trying to interact with a lot of users to get their opinions. We’ve got an amazing opportunity to rebuild eBays interface from the ground up and want to make sure every aspect of using eBay gets improved in the process. So feel free to send me your thoughts through this blog or on the beta site.

If you want to see a demo of the application, I gave a presentation recorded at Adobes ApolloCamp back in March. http://video.onflex.org/2007/03/26/apollo-camp-ebay-and-effectiveui-and-artemis-sean-christmann/