Badly written Flash runs faster than even worse written HTML5

News at 11.

John Nack links to what Chris Black claims shows HTML5 being massively outperformed by Flash on mobile devices. Here’s the HTML5 link itself.

It’s a shame the JavaScript is so awful, e.g. here’s the refresh code (the comments are mine):

ctx.clearRect(0, 0, 500, 600) // erase the entire background, omit the semicolon because you can!
ctx.fillStyle = ‘rgb(255,255,255)’; // set the background color to white
ctx.fillRect (0, 0, 500, 500); // erase it all again to be sure
ctx.fillStyle = ‘rgb(0,0,0)’;
ctx.beginPath();
ctx.arc(x, y, 20, 0, Math.PI * 2, true);
ctx.fill();
ctx.fillStyle = ‘rgb(128,255,128)’;
ctx.fillRect (0, 500-32, 500, 32); // draw the non-moving element every frame

You can be pretty sure that if you create a bouncing ball animation in Flash (and to do so you need not write a single line of code, which is definitely an advantage for Flash right now), Flash will not be so stupid as to erase the entire background twice each frame. In fact, Flash is highly optimized to draw as little as possible each frame (in fact the SWF format also supports encoding deltas in the animation frames themselves, if I recall correctly, which means the runtime doesn’t even need to figure out how to minimally update the screen).

Anyway, I changed the code to (a) not erase the background even once each frame, and (b) only erase a reasonably minimal area around the previously rendered “ball” and voila, massive framerate increase:
ctx.fillStyle = ‘rgb(255,255,255)’;
ctx.fillRect (lastball.x – 21, lastball.y – 21, 42, 42);
ctx.fillStyle = ‘rgb(0,0,0)’;
ctx.beginPath();
ctx.arc(x, y, 20, 0, Math.PI * 2, true);
lastball.x = x; // lastball is a global variable (eeew) = {x:0,y:0}
lastball.y = y;
ctx.fill();
ctx.fillStyle = ‘rgb(128,255,128)’;
ctx.fillRect (0, 500-32, 500, 32);

So, vaguely apples-to-apples comparison, at least without zooming, HTML5 — when animating circles and rectangles — actually slightly outperforms Flash (based on my 150% framerate increase from a trivial optimization). It’s clear HTML5 is horribly unoptimized beyond this — e.g. zooming in* slows down the framerate significantly, even when the animation itself is offscreen (seriously, get an intern on that stat, because not drawing stuff which is off-screen is not a rocket science optimization).

Note: * on an iOS device. It seems to work just fine on the desktop.

As an aside: the way you’d optimize this properly in practice would be to clip all the rendering to an update region based on what’s moving around. (This would also optimize the redrawing of the static element.) If I were writing the backend of a tool that provided artists with the ability to create HTML5 animations, this is exactly how I would mechanically optimize the output at first pass. The way Flash optimizes (at least, iirc, by analyzing the vectors at “compile” time and optimizing deltas) is even more sophisticated and has an even greater upside. So the real question to my mind is, why does Flash perform so badly?

Further aside: I’ve put a slightly more tuned version here. It uses a simple and more efficient method for calculating fps, removes the fps cap (of ~60fps), and reduces the amount of the green rectangle redrawn every frame. Incidentally, one thing I am consistently seeing on my iPhone 4 is that the frame rate appears to be capped (battery conservation, perhaps?).

I can get well over 75fps zoomed in on the animation on my iPad, but the iPhone won’t get above around 20 fps. Similarly, both versions run around 100fps on my Macbook Pro, indicating that SetInterval (or something) seems to be capped in Safari.

Final Thoughts

You might make the argument that “well, this is hand-tuned HTML5 code vs. a simple, unoptimized Flash animation”. But aside from the fact that I haven’t really tuned this code (and, arguably, the original test oddly favored Flash by featuring a large rect with nothing going on in it), the optimizations I did are nowhere near as clever as the obvious optimizations a decent tool that outputs canvas drawing commands would do. And in fact they’re not nearly as clever as what Flash is already doing (which is kind of sad, really).

On top of this, the mobile Webkit HTML5 engine clearly has a huge amount of headroom in terms of optimization; Flash is a very mature product, and version 10.1 was brought to market specifically to address performance concerns; if it has a lot of “low hanging fruit” in terms of optimization, I’d be very surprised.

PHP json_encode replacement

I ran into a problem with RiddleMeThis recently — the new online runtime needs to generate JavaScript structures on the server to hand over to the client. To do this I used the json_encode function, which requires PHP 5.2. Until now, RiddleMeThis hasn’t made many assumptions about the PHP runtime, but it turns out assuming PHP 5.2 is not a good idea. There’s a chunk of PHP you can get somewhere or other that will replace json_encode, but it’s annoyingly inconvenient.

Anyway, it turns out I wrote my own jsencode() function in order to deploy an earlier version of the runtime on a Mac OS X 10.5 server (which doesn’t have PHP 5.2, argh). This was a quick and dirty effort which served the purpose but is kind of evil (it wraps quotation marks around numbers, for one thing, and doesn’t quote the symbols — which is fine for JavaScript but not allowed for JSON, especially if you’re using a strict parser as found in jQuery 1.4.

Feel free to use either of these snippets as you please.

function jsencode( $obj ){
	if( is_array( $obj ) ){
		$code = array();
		if( array_keys($obj) !== range(0, count($obj) - 1) ){
			foreach( $obj as $key => $val ){
				$code []= $key . ':' . jsencode( $val );
			}
			$code = '{' . implode( ',', $code ) . '}';
		} else {
			foreach( $obj as $val ){
				$code []= jsencode( $val );
			}
			$code = '[' . implode( ',', $code ) . ']';
		}
		return $code;
	} else {
		return '"' . addslashes( $obj ) . '"';
	}
}

So, here’s a better version. It allows you to encode for JSON or (by default) JavaScript (useful for passing stuff from PHP server-side to JavaScript client-slide):

function jsencode( $obj, $json = false ){
	switch( gettype( $obj ) ){
		case 'array':
		case 'object':
			$code = array();
			// is it anything other than a simple linear array
			if( array_keys($obj) !== range(0, count($obj) - 1) ){
				foreach( $obj as $key => $val ){
					$code []= $json ?
						'"' . $key . '":' . jsencode( $val ) :
						$key . ':' . jsencode( $val );
				}
				$code = '{' . implode( ',', $code ) . '}';
			} else {
				foreach( $obj as $val ){
					$code []= jsencode( $val );
				}
				$code = '[' . implode( ',', $code ) . ']';
			}
			return $code;
			break;
		case 'boolean':
			return $obj ? 'true' : 'false' ;
			break;
		case 'integer':
		case 'double':
			return floatVal( $obj );
			break;
		case 'NULL':
		case 'resource':
		case 'unknown':
			return 'null';
			break;
		default:
			return '"' . addslashes( $obj ) . '"';
	}
}

To send the information from PHP to JavaScript, you’d write something like this:

<script type="text/javascript">
      var foo = <?php echo jsencode( $some_variable ); ?>;
</script>

To generate a JSON feed using this code you’d write something like this:

header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // some time in the past
header('Content-type: application/json');
echo jsencode( $some_associative_array, true );