How To Minimize AMF Serialization Size
I’ve talked about AMF serialization size before, but there’s one tip I left out. Today’s article shows you one crucial step you need to take to make sure your AMF data is packed as tightly as possible so you’re not wasting file size or bandwidth.
Astute readers may have actually spotted today’s trick hidden and unmentioned in AMF Serialization Tricks. It has to do with the flash.net.registerClassAlias
function that controls the name of your class objects when serialized to AMF, such as with ByteArray.writeObject
. If you don’t call registerClassAlias
at all, then all of your objects will be written out without a class name as the AMF equivalent of a plain Object
. For example:
var bytes:ByteArray = new ByteArray(); bytes.writeObject(new MyClass());
This usually isn’t the desired option since one of AMF’s major features is its ability to give you nicely typed objects when you ultimately deserialize your AMF data via a function like ByteArray.readObject
. So, you call registerClassAlias
like so:
package com.mycompany.geom { public class Parallelepiped {} } registerClassAlias("com.mycompany.geom.Parallelepiped", Parallelepiped); var bytes:ByteArray = new ByteArray(); bytes.writeObject(new Parallelepiped());
Now you’ve successfully stored your object with its fully-qualified class name. The package name and the class name are stored, so you don’t have to worry about conflicts when two classes in different packages share the same name.
But there is a problem with this: the whole class name is stored in the AMF data. This fully-qualified class name is quite long and adds quite a lot of overhead. Fortunately, there’s no rule that the string you pass to registerClassAlias
has to be the fully-qualified class name. You can take shortcuts here and use only as many characters as you need. Usually, you just need one:
package com.mycompany.geom { public class Parallelepiped {} } registerClassAlias("P", Parallelepiped); var bytes:ByteArray = new ByteArray(); bytes.writeObject(new Parallelepiped());
In this version only a “P” is stored as the class name. While this would be a terrible class name in the AS3 code, it’s only used for AMF serialization and deserialization so the rest of your code is unaffected. You don’t have to rename your classes. It’s not particularly obvious when trying to guess what the AMF data’s contents are, but this is often less important than minimizing the AMF data’s size. One idea is to use a “key” or “legend” of mappings from fully-qualified class name to AMF alias. You might even do this in your code where you have all your registerClassAlias
calls. Or you could externalize it to another file with the rest of your app’s documentation.
Given these downsides, what kind of upsides can we expect in the form of AMF data size reductions? Here’s a sample app to find out:
package net.somebody.foo.goo.utils { public class SomeClassWithAReallyReallyLongName { public var i:int; public var s:String; public var b:Boolean; public var n:Number; } }
package { import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.net.registerClassAlias; import flash.utils.ByteArray; import net.somebody.foo.goo.utils.SomeClassWithAReallyReallyLongName; public class AMFSize extends Sprite { private var logger:TextField = new TextField(); private function row(...cols): void { logger.appendText(cols.join(",")+"\n"); } public function AMFSize() { logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); row("Class Name", "Size"); var plainBytes:ByteArray = new ByteArray(); plainBytes.writeObject(new SomeClassWithAReallyReallyLongName()); row("{Plain}", plainBytes.length); registerClassAlias( "net.somebody.foo.goo.utils.SomeClassWithAReallyReallyLongName", SomeClassWithAReallyReallyLongName ); var longBytes:ByteArray = new ByteArray(); longBytes.writeObject(new SomeClassWithAReallyReallyLongName()); row("SomeClassWithAReallyReallyLongName", longBytes.length); registerClassAlias("C", SomeClassWithAReallyReallyLongName); var shortBytes:ByteArray = new ByteArray(); shortBytes.writeObject(new SomeClassWithAReallyReallyLongName()); row("C", shortBytes.length); } } }
And here are the results:
Class Name | Size |
---|---|
{Plain} | 24 |
SomeClassWithAReallyReallyLongName | 85 |
C | 25 |
So with just one simple change from a long fully-qualified class name to a single character the total AMF size drops from 85 bytes to 24. While 61 bytes isn’t a big deal, it adds up when storing more objects and more types of objects in a more complicated serialization. If you’re using fully-qualified class names in your project’s AMF serialization, try switching to single characters and measure the results for yourself. It just might be an easy savings to bandwidth or file size.
Spot a bug? Have a question or suggestion? Post a comment!
#1 by Mauric on September 7th, 2013 ·
Actually, the article is missing some important aspects of AMF serialization
1) variable names are also included in the stream, along with the class alias (this is for sanity-checking in case the class definition changes). So even if you can alias the class with a shorter name, there is no way of aliasing variable names.
People often use “readable” variable names, and not the one letter variable that you used in your example.
2) but the most important thing that is missing is that serialization will build a global dictionary of class definitions at the begining of the stream for EACH writeObject statement.
If you serialize you data using 100 writeObject, you will get 100 times the global dictionary.
However, if the object you write includes 100 instances of the same class, the class definition will be written only once.
So one important tip to save space is to serialize ALL your data in ONE single writeObject() statement, so that class defintions are shared.
#2 by jackson on September 18th, 2013 ·
These are excellent suggestions. I’ve now addressed both of them in followup articles. For variable names I’ve shown a trick using getters, setters, and the
[Transient]
metadata to reduce AMF size. For the global dictionary, I’ve created a utility class calledAMFObjectPooler
to collapse multiplewriteObject
calls into just one.#3 by Caius on December 26th, 2013 ·
I wrote another article starting from you previous article:
http://jacksondunstan.com/articles/2248/comment-page-1#comment-152405
and put everything in one article.
http://www.aymericlamboley.fr/blog/amf-p2p-serialization-tricks/
I used this technique to optimize the comunication between server -> clients for the football game i build using startling & feathers
http://www.bilub.com
#4 by jackson on December 26th, 2013 ·
That’s a nice, real-world explanation of how to optimize the size of your AMF data to reduce network bandwidth usage. Thanks for the link!