jueves, julio 09, 2009

Introducing Tomahawk's SerialBoost

The problem:

Currently our Engine, and so our game too, heavyly relay on Xml serialization. Tomhawks has a very powerful and flexible archetype system, that allows to instantiate logic objects using different initial value sets for their properties.

Currently archetype system allows placing any public variable of the logic object class in the archetype, as well as inlining the archetype of a smaller child object directly inside the archetype of the father one. The archetype has an open format (the order of the elements inside doesn't have to match the code, or have any particular order) and the number of fields specified is also optional (not all fields must be specified). This flexibility comes in part thanks to the use of XmlSerialize.

XmlSerialize is well know to work very slowly on PC, and terribly slow in the Xbox. This is because XmlSerialize requires a lot of Reflection, which also know to be not very fast. When we started the development of Rotoroscope, and it's engine, that wasn't a problem, because the number of logic objects and the size of its archetypes was small. Now, after 6 month of adding feature after feature, the number of archetypes has multiplies, as well as their size.

So, we had long benefit from the convenience of XmlSerialize during the development process, but now we have a puzzle game with a loading screen that takes longer that a AAA FPS game... and that's a problem :D

The Solution:

Fortunately, Tomahawk is modulated designed and all the serialization system can be replaced without touching any logic code, in theory at least. But... what can use instead of XmlSerialize?

For the PC, we found a tool by Microsoft called SGEN. This tool takes a .NET assembly as input, and outputs another assembly with a newly created class for each one of the serializable class in the input assembly. These new classes contain specialized code that eliminate the XmlSerialize use of Reflection, boosting up the speed of XmlSerialize. We added a single line to the compile script in the PC project, and the loading times were reduced by almost 85%.

Whoa! That's a hell of an optimization. But what about the Xbox?

I tried to use SGEN in the Xbox solution, but it turns out that it couldn't reflect the Xbox assemblies, and consequently generate the serialization optimizers. Then I tried to decompile the PC serialization optimizers, to recompile for Xbox, that didn't work too because the classes generated by SGEN were subclass of a .NET class that is not present in the Compact Framework. That was like hitting a wall, SGEN won't ever work for the Xbox, so I abandoned that way.

Anyway SGEN philosophy was the way to go, so if I can't use SGEN directly, I will have to replicate it's effects in some way. So I decided to create a similar approach from scratch, specially optimized for the Xbox.

SerialBoost:

I've started working in a framework that allow me to create a serialization specialized class for every logic class that requires serialization in Tomahawk and Rotoroscope. This framework will supply a base class compatible with Xbox (not like SGEN) that will simplify and reduce at a minimum the lines of code required for each serialization class.

Those specialized classes will be auto-generated with a command-line tool, at design time, and then deployed to the Xbox with the rest of the game. They will be indexed at startup, so no subsequent use of Reflection will be needed.

Then, when serializing or deserializing an object to/from Xml, the framework will look up for a specialized class to do so. If it's found, it will be used, and if it's not, it will fall back to standard XmlSerialize, making the presence of these generated classes optional.

I called this framework (and so the singleton class to access it): BoostSerialize. This class provides methods to create objects from a memory stream (containing the Xml) and viceversa. The interface is simple, you supply a memory stream containing Xml and the Type you wan to create with it. It will give you an object in return created with the fastest method available per each class.

Current State:

The first version of the framework is currently working in a isolated solution, so it's not currently integrated with the rest of the engine. As soon as I had the first working pieces of code, I created a small benchmark for the Xbox, to make sure the efforts will pay in the final game.

For testing purposes I took the SaveGame object from Rotoroscope, and I artificially extended it to cover all serialization cases (base types, enums, structs, other arquetipable objects, etc.). Then created a sample application that will load lots of them from disk, measuring the time spent using both XmlSerialize and SerialBoost.

These are the results, for 500 objects loaded:

* XmlSerialize took 3.17 sec.
* BoostSerialize took 0.72 sec.

And these are for 5.000 objects loaded:

* XmlSerialize took 30.16 sec.
* BoostSerialize took 7.14 sec.

Conclusion

These results makes it really worthy to spend a few more days creating the code generator, and integrating the whole system into Tomahawk. It will be risky to do such a massive change at this point of the development, but it will pay with a really noticeable improvement on loading times, which will sure produce a better playing experience for the players.

So, stop talking, and keep coding! More on Tomahawk's SerialBoost soon, I will publish the final effects on the loading time as soon as I finish and integrate it into the game.

Regards!

No hay comentarios: