Serialize Anything: Part 2
One of the new features in Flash Player 11 is a native JSON encoder/decoder class. In the Serialize Anything article, I neglected to add JSON as an option for serializing and deserializing arbitrary objects. In today’s followup we’ll take a look at the performance of the native JSON class and compare it to ByteArray.readObject/writeObject
and XML.
The built-in JSON
class has only two static methods: stringify
(serialize) and parse
(deserialize). The following test app is based on the test app from the first article with a few changes:
JSON.stringify
added as a serialization option- Manual JSON string building added as a serialization option
JSON.parse
added as a deserialization option- Reporting compressed serialized data size to get a more detailed picture of the serialization options
- Flex SDK (MXMLC) 4.5.1.21328, compiling in release mode (no debugging or verbose stack traces)
- Release version of Flash Player 11.1.102.55
- 2.4 Ghz Intel Core i5
- Mac OS X 10.7.2
- Manual creation of JSON is much (~10x) faster than
JSON.stringify
(Update: this manual creation routine does not escape strings. A version that escapes strings will be somewhat slower.) - Manual creation of JSON is quite a bit (~3x) quicker than manual creation of XML.
ByteArray.readObject
andByteArray.writeObject
are still the speed kings for serialization and deserialization- JSON is slightly smaller in both uncompressed and compressed form
ByteArray
is about 7x smaller than either JSON or XML in uncompressed form and 10x smaller in compressed form
Here is the test app:
package { import flash.display.*; import flash.utils.*; import flash.text.*; import flash.net.*; public class SerializeAnything2 extends Sprite { private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } private function row(...cols): void { __logger.appendText(cols.join(",")+"\n"); } public function SerializeAnything2() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; var logger:TextField = __logger; logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); var beforeTime:int; var afterTime:int; var i:int; var SIZE:int = 100000; registerClassAlias("Person", Person); var people:Vector.<Person> = new Vector.<Person>(SIZE); for (i = 0; i < SIZE; ++i) { var person:Person = new Person(); person.secret = "I'm a Canadian"; person.first = "William"; person.last = "Shatner"; person.age = 80; people[i] = person; } row("Operation", "Time"); beforeTime = getTimer(); var ba:ByteArray = new ByteArray(); ba.writeObject(people); afterTime = getTimer(); row("ByteArray serialize", (afterTime-beforeTime)); beforeTime = getTimer(); var jsonString:String = "["; for each (person in people) { jsonString += "{\"first\":\"" + person.first + "\",\"last\":\"" + person.last + "\",\"age\":" + person.age + ",\"secret\":\"" + person.secret + "\"},"; } jsonString = jsonString.substr(0, jsonString.length-1) + "]"; afterTime = getTimer(); row("JSON serialize (manual)", (afterTime-beforeTime)); beforeTime = getTimer(); jsonString = JSON.stringify(people); afterTime = getTimer(); row("JSON serialize (stringify)", (afterTime-beforeTime)); beforeTime = getTimer(); var xmlString:String = "<people>"; for each (person in people) { xmlString += "<person first=\"" + person.first + "\" last=\"" + person.last + "\" age=\"" + person.age + "\" secret=\"" + person.secret + "\" />"; } xmlString += "</people>"; var xml:XML = new XML(xmlString); afterTime = getTimer(); row("XML serialize", (afterTime-beforeTime)); beforeTime = getTimer(); ba.position = 0; people = ba.readObject() as Vector.<Person>; afterTime = getTimer(); row("ByteArray deserialize", (afterTime-beforeTime)); beforeTime = getTimer(); people = new Vector.<Person>(); var jsonPeople:Array = JSON.parse(jsonString) as Array; for each (var jsonPerson:Object in jsonPeople) { person = new Person(); person.first = jsonPerson.first; person.last = jsonPerson.last; person.age = jsonPerson.age; person.secret = jsonPerson.secret; people.push(person); } afterTime = getTimer(); row("JSON deserialize", (afterTime-beforeTime)); beforeTime = getTimer(); people = new Vector.<Person>(); for each (var personNode:XML in xml.person) { person = new Person(); person.first = personNode.@first; person.last = personNode.@last; person.age = personNode.@age; person.secret = personNode.@secret; people.push(person); } afterTime = getTimer(); row("XML deserialize", (afterTime-beforeTime)); row(); row("Format", "Uncompressed Size", "Compressed Size"); var uncompressedSize:int = ba.length; ba.position = 0; ba.compress(); row("ByteArray", uncompressedSize, ba.length); ba.length = 0; ba.writeMultiByte(jsonString, "us-ascii"); ba.position = 0; uncompressedSize = ba.length; ba.compress(); row("JSON", uncompressedSize, ba.length); ba.length = 0; ba.writeMultiByte(xmlString, "us-ascii"); ba.position = 0; uncompressedSize = ba.length; ba.compress(); row("XML", uncompressedSize, ba.length); } } } class Person { public var first:String; public var last:String; private var __secret:String; public var age:int; public function set secret(secret:String): void { __secret = secret; } public function get secret(): String { return __secret; } }
I ran this test with the following environment:
And got these results:
Operation | Time |
---|---|
ByteArray serialize | 125 |
JSON serialize (manual) | 178 |
JSON serialize (stringify) | 1939 |
XML serialize | 591 |
ByteArray deserialize | 103 |
JSON deserialize | 577 |
XML deserialize | 451 |
Format | Uncompressed Size | Compressed Size |
---|---|---|
ByteArray | 1000063 | 2048 |
JSON | 7200001 | 24528 |
XML | 7400017 | 25220 |
So we see that this test has revealed some interesting data about serialization and deserialization:
The obvious conclusion from the above is that ByteArray
is the superior serialization format when compared to JSON and XML. It’s faster to serialize, faster to deserialize, smaller when either uncompressed or compressed, supports AS3 types via flash.net.registerClassAlias
, and requires no AS3 code to serialize it quickly. What more could you want?
Spot a bug? Have a tip about serialization or deserialization? Post a comment!
#1 by Henke37 on January 16th, 2012 ·
Uhm, why are you even doing string concatenation for the XML generation? Why aren’t you using an XML literal instead? They are allowed to have AS expressions in them! You have no excuse.
#2 by jackson on January 16th, 2012 ·
I went with the string concatenation approach because it’s faster than using
XML
objects to build an XML document. This was also true in 10.1 and 10.2.#3 by Amit Patel on January 17th, 2012 ·
Interesting. I expected JSON.stringify to be faster than XML construction.
Note that the “manual” JSON serialization isn’t doing the same thing as JSON.stringify — it doesn’t escape the strings. Everything will seem fine for a while and then boom, Little Bobby Tables will come along and your code will mysteriously break. You can skip escaping only in a limited number of situations. A more fair comparison would be to manually construct the JSON with calling an escaping function.
#4 by jackson on January 17th, 2012 ·
JSON.stringify
may be as fast as constructing XML with theXML
class, but as covered before that is far slower than string concatenation so I didn’t even attempt it.As for escaping strings, here’s what I get if I change
SIZE
to 2 and print outjsonString
after each JSON serialization:It looks pretty escaped to me, but maybe I’m missing something.
#5 by NemoStein on January 17th, 2012 ·
Jackson, I can see a quote (‘) there, but not a double quote (“).
#6 by jackson on January 17th, 2012 ·
I think I’m still missing something. Where do you see the single quote?
#7 by NemoStein on January 17th, 2012 ·
Haha, sorry, I’ll try to be a little more descriptive next time.
I was talking about this single quote: “I’m a Canadian”
It looks fine, ok ok!
But, if you serialize a String with double quotes, like: Free as in “free beer”.
I would expect: {“text”:”Free as in \”free beer\”.”}
Your JSON concatenation doesn’t take it into account.
#8 by jackson on January 17th, 2012 ·
Ah, I see what you mean now. Yes, I suppose I should have escaped double quotes. That would definitely slow down the “manual” JSON creation as well as the XML creation, but probably not nearly enough to make them slower than
JSON.stringify
or theXML
class.Thanks for pointing this out. I’ll update the article with a disclaimer.
#9 by skyboy on January 19th, 2012 ·
I’d just like to note that my JSON class can serialize objects much faster than the native alternative (on my machine, at least; 3x faster or more). Parsing varies much more, being anywhere from close to a 1:1 on older machines (due to the aversion to allocating) to 3:1 on newer machines (due to faster RAM). To avoid the name conflict, the
encode
method can be used in place ofstringify
(otherwise, include the full class path:skyboy.serlization.JSON.stringify
).The actionJson package is faster at encoding than my own.
#10 by Islam on March 4th, 2013 ·
well, looks like at the beginning you gave an advantage to ByteArray by using for-each loop for it’s competitors. Isn’t it 4 times slower for fixed vectors then simple for loop?