-
-
Notifications
You must be signed in to change notification settings - Fork 55
Optimization & Pitfalls
There are a lot of things you can do to optimize performance. Here are things you can do, (todo: write some examples for everything as well)
-
Always use the
int Serialize(ref byte[], ...)
method instead of the one that returns a byte-array. If Ceras has to create a byte array for you that is exactly the right size then there's no other way than to allocate a new array after serialization and copy everything over. That takes time, so avoid it. -
You should always reuse the byte-array you're passing to
Serialize()
. Allocating a new array is very wasteful (see note at bottom). Optionally you can also pass innull
, so Ceras will allocate a buffer with the optimal size for you (where as optimal means "optimal for the CPU", so pretty large, 4KB-64KB). That way the amount of re-allocations is minimized. -
When deserializing always try to reuse objects. Use the
Deserialize(ref T existingObject)
method whenever possible. Obviously sometimes that's not possible when you actually want 'new' objects, but if you're for example just receiving network packets, then reusing them is a very good idea. -
Make use of
config.KnownTypes
andceras.ProtocolChecksum
is really easy. Even if your serialization has nothing to do with sending messages over the netKnownTypes
can definitely still improve performance! Read the Type Encoding Guide. -
One of Ceras' special features is that it preserves references, meaning it can correctly handle "reference loops" (for example two objects referencing each other). You can set
config.PreserveReferences = false;
to disable this feature for a notable performance boost! -
Ceras can apply special optimizations for fields/properties where the type is
sealed
. (Make sure the field/prop is actually defined using the sealed type. Something likeISomeInterface myField = new SealedClass();
will not work.) -
If you are concerned about allocations and GC-pressure (and if you're using Unity or creating any other high-performance application you should be!) then take a look at
config.ObjectFactory
andconfig.DiscardObject
. Implement your own pool for your objects, there's a tutorial step for that as well that shows how to do it. -
Avoid boxing! (Read up on boxing and value-types if you don't know what that means)
-
Use "blittable" types if you can, so Ceras can use its so called "ReinterpretFormatter" which is really fast. You can expect speedups on the order of 10-20x! (More Info)
-
Prefer concrete classes over dictionaries! Let's assume for example you're serializing something like a
Dictionary<string, object>
and you know it will always contain the same keys (like{ x:4, y:3, name: "test" }
), then you should consider creating a concrete class for it instead (likeclass Thing { int x; int y; string name; }
).
Some things to look out for / keep in mind:
-
The type you serialize must exactly match the one you deserialize with again! What does that mean? It means if you do
ceras.Serialize(myObject);
then you are actually callingceras.Serialize<MyObjectType>(myObject);
so that means when deserializing you must use the same type, like this:ceras.Deserialize<MyObjectType>(...);
. You can not use<MyObjectType>
for serialization and<object>
for deserialization, because they have to be the same. When in doubt you can just always use<object>
though! So that means you could just:ceras.Serialize<object>(myObject);
and thenceras.Deserialize<object>(...);
that would work, but is somewhat less efficient than using the specific types directly. -
Some of your members are not getting serialized? No clue why and what's going on? Call
ceras.GenerateSerializationDebugReport(type)
to get a detailed, easy to read, report about what members are included or not and why! -
When using the VersionTolerance feature, you can't change types of fields. For example if you have a
public int Number;
and then later change it topublic float Number;
it will not work. Ceras supports removing, renaming, and adding new fields (or properties), but changing the type of an existing member will not work. If you need that feature let me know and I'll add it. -
You probably often want to change some settings, so you're passing a
SerializerConfig
when constructingCerasSerializer
, but you must be aware that any changes you make to the configuration can potentially make previously serialized data incompatible. I know that this is probably super-obvious, but I better list it here anyway. Like if you mess around withTargetMembers
, setting it to None, serializing an object, and then setting it to All and deserializing it, it should be no surprise that everything breaks.... -
Assuming you are using Ceras in some advanced networking scenario; lets say as a packet serializer, and you're writing some sort of server that can talk to multiple clients: never ever share a serializer for all clients (unless you know what you're doing and are fully aware of how Ceras does internal type and object caching). When Ceras "learns" new types from one client, then the other client might not know that type already. So create a new CerasSerializer instance for every client. (There are ways around that though, just open an issue or message me on discord if you want to optimize that far).
-
Hot-swapping the SchemaFormatter of a value type in a reference-object is not supported yet. It's a rare scenario, so if you need it open an issue and describe what you're doing. There are workarounds for it as well.
-
Serializing delegates (Action, Func, ...) is not supported because its absolutely insane to do. You could possibly serialize delegates to static methods without any problems, but serialization of lambda-expressions is where things get super-complicated. If you have need for this contact me and we'll figure it out.Ceras now supports serializing delegates. You can enable this in the options. If you don't the exception will tell you exactly what to do when a delegate is encountered. -
Ceras uses
.Equals()
and.GetHashCode
on your objects, so if you have overriden them, make sure they are implemented correctly! So called 'degenerate' cases for GetHashCode can pretty much kill performance.
(*) Note: The allocation itself is not wasteful. But allocating a byte-array, using it, and then just throwing it away is wasteful because it puts pressure on the garbage collector (more stuff to clean up).